[Trick] Легальный способ доступа к закрытым членам
От: rg45 СССР  
Дата: 25.09.18 14:32
Оценка: 81 (13)
Наверное, у каждого бывали в жизни случаи, когда очень хотелось получить несанкционированный доступ к закрытым членам какого-то класса. Ну вот понимаешь, что нельзя, но очень хочется. А знаете ли вы, что это можно сделать абсолютно легально и в соответствии со стандартом С++? Ну, кто-то знает, а кто-то нет. Фокус в том, что существует один сценарий, когда мы имеем полное право использовать имя закрытого члена класса. Это случай явного инстанцированя какого-нибудь шаблона:

17.7.2 Explicit instantiation
12 The usual access checking rules do not apply to names used to specify explicit instantiations.


То есть мы можем обращаться к закрытому члену класса при явном инстанцировании какого-нибудь шаблона, параметром которого является этот член. Таким образом, задача сводится к тому, чтобы, при инстанцированиии шаблона класса, сохранить значение его определенного параметра, имеющего тип указателя на член, в определенную статическую переменную такого же типа. Добавляем синтаксического сахарку и дело, как говорится, в шляпе.

Эскизный пример реализации и использования здесь:

https://ideone.com/ttt4pI [Upd]: пофикшена ошибка, ссылка обновлена

Использование выглядит примерно так:

class A
{
public:
 
   explicit A(int value) : m_value(value) { }
 
   int GetValue() const { return m_value; }
 
private:
   int m_value;
};
 
ENABLE_PIVATE_MEMBER_ACCESS(A_value, A, int, m_value)
 
int main()
{
   A a(1);
   std::cout << "a.GetValue(): " << a.GetValue() << std::endl;
   *PrivateMemberAccessor<A_value>(a) = 42;
   std::cout << "a.GetValue(): " << a.GetValue() << std::endl;
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 25.09.2018 18:10 rg45 . Предыдущая версия . Еще …
Отредактировано 25.09.2018 14:49 rg45 . Предыдущая версия .
Re: [Trick] Легальный способ доступа к закрытым членам
От: Nikе Россия  
Дата: 25.09.18 14:54
Оценка: 9 (1) :)
Здравствуйте, rg45, Вы писали:

А зачем так заморачиваться, если 100 лет есть паттерн: Public Morozov?
Нужно разобрать угил.
Re[2]: [Trick] Легальный способ доступа к закрытым членам
От: Nikе Россия  
Дата: 25.09.18 14:55
Оценка:
Здравствуйте, Nikе, Вы писали:

N>А зачем так заморачиваться, если 100 лет есть паттерн: Public Morozov?


Хотя... Это одна из его возможных реализаций, так что ок.
Нужно разобрать угил.
Re[2]: [Trick] Легальный способ доступа к закрытым членам
От: rg45 СССР  
Дата: 25.09.18 14:57
Оценка:
Здравствуйте, Nikе, Вы писали:

N>А зачем так заморачиваться, если 100 лет есть паттерн: Public Morozov?


Ну чисто из академических соображений. Просто чтоб знать, что есть вот еще такой способ.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: [Trick] Легальный способ доступа к закрытым членам
От: kov_serg Россия  
Дата: 25.09.18 15:47
Оценка: +1 -2 :))) :)))
Здравствуйте, rg45, Вы писали:

R>Эскизный пример реализации и использования здесь:

R>https://ideone.com/meuOaW
Это должно быть здесь
Re[2]: [Trick] Легальный способ доступа к закрытым членам
От: Nikе Россия  
Дата: 25.09.18 15:56
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Это должно быть здесь

_>Image: 2013_09_10_12_12_allunix_ru_back_screamingrobot.jpg
А почему бы и нет?
Нужно разобрать угил.
Re[3]: [Trick] Легальный способ доступа к закрытым членам
От: Vain Россия google.ru
Дата: 25.09.18 21:44
Оценка:
Здравствуйте, Nikе, Вы писали:

_>>Image: 2013_09_10_12_12_allunix_ru_back_screamingrobot.jpg

N>А почему бы и нет?
Новый тренд: русские программисты (на манер британских учоных)
Когда им нечем заняться, они придумывают публичный доступ к закрытым данным.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Отредактировано 25.09.2018 21:48 Vain . Предыдущая версия .
Re[2]: [Trick] Легальный способ доступа к закрытым членам
От: andrey.desman  
Дата: 25.09.18 23:51
Оценка: +2
Здравствуйте, Nikе, Вы писали:

N>А зачем так заморачиваться, если 100 лет есть паттерн: Public Morozov?



Паблик М. может сборку навернуть, т.к. уровень доступа может участвовать в имени методов (mangling).
Re[4]: [Trick] Легальный способ доступа к закрытым членам
От: B0FEE664  
Дата: 26.09.18 07:39
Оценка: +1 :))) :)))
Здравствуйте, Vain, Вы писали:

N>>А почему бы и нет?

V>Новый тренд: русские программисты (на манер британских учоных)
V>Когда им нечем заняться, они придумывают публичный доступ к закрытым данным.

Russian hackers, sir.
И каждый день — без права на ошибку...
Re: [Trick] Легальный способ доступа к закрытым членам
От: _NN_ www.nemerleweb.com
Дата: 27.09.18 07:27
Оценка:
Здравствуйте, rg45, Вы писали:

Хотелось бы тут выводить типы A и int автоматически.

R>ENABLE_PIVATE_MEMBER_ACCESS(A_value, A, int, m_value)


Использование decltype в специализации компилируется, но любые попытки использовать дальше не увенчались успехом.
Есть идеи ?

template<typename MemberPtrT, MemberPtrT>
struct X;

template<> struct X<decltype(&A::m_value), &A::m_value> // : Base<decltype(&A::m_value)> // нет доступа
{
    // using Q = decltype(&A::m_value);  // нет доступа
};
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[2]: [Trick] Легальный способ доступа к закрытым членам
От: rg45 СССР  
Дата: 27.09.18 08:09
Оценка: +1
Здравствуйте, _NN_, Вы писали:


_NN>Использование decltype в специализации компилируется, но любые попытки использовать дальше не увенчались успехом.

_NN>Есть идеи ?

Убил на это уйму времени, результат — ноль

Я пробовал обеспечить доступ через дружественные фунции. На msvc работает, на gcc — нет. Причем, gcc прав

P.S. Кстати, если бы это удалось сделать, то одновремено с этим получилось бы сделать указатель на член constexpr, а не защелкивать его в runtime переменной, как сейчас.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 27.09.2018 8:19 rg45 . Предыдущая версия . Еще …
Отредактировано 27.09.2018 8:11 rg45 . Предыдущая версия .
Отредактировано 27.09.2018 8:11 rg45 . Предыдущая версия .
Re[3]: [Trick] Легальный способ доступа к закрытым членам
От: _NN_ www.nemerleweb.com
Дата: 27.09.18 08:45
Оценка:
Здравствуйте, rg45, Вы писали:

R>Я пробовал обеспечить доступ через дружественные фунции. На msvc работает, на gcc — нет. Причем, gcc прав


Где код ?
Может получиться что-нибудь придумать.

R>P.S. Кстати, если бы это удалось сделать, то одновремено с этим получилось бы сделать указатель на член constexpr, а не защелкивать его в runtime переменной, как сейчас.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: [Trick] Легальный способ доступа к закрытым членам
От: rg45 СССР  
Дата: 27.09.18 08:57
Оценка:
Здравствуйте, _NN_, Вы писали:

R>>Я пробовал обеспечить доступ через дружественные фунции. На msvc работает, на gcc — нет. Причем, gcc прав


_NN>Где код ?

_NN>Может получиться что-нибудь придумать.

Не сохранился, восстановлю, как появится свободная минутка. Идея основывается на том, что в некоторых случаях ADL заглядывает внутрь классов в поисках дружественных функций. Только по стандарту, это делается только в том случае, если в сигнатуре функции присутствует какая-то связь с классом, дружественной которому эта функция является. gcc постуает строго, как написано в стандарде. А msvc более "либеральна" в этом плане. Таким образом нужный указатель на член можно запаковать в тип возвращаемого значения, а потом вытащить его при помощи внешней метафункции.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: [Trick] Легальный способ доступа к закрытым членам
От: kov_serg Россия  
Дата: 27.09.18 12:59
Оценка:
Здравствуйте, rg45, Вы писали:

R>Наверное, у каждого бывали в жизни случаи, когда очень хотелось получить несанкционированный доступ к закрытым членам какого-то класса.


Поэтому я по возможности предпочитаю такие приватные поля.
// Public.h
struct Public {
    static Public* create(int value);
    virtual int getValue() const=0;
    virtual ~Public() {}
};

// Public_create.cpp
#include "public.h"
#include "a_lot_of_trash_and_other_dependencies"

struct Private : Public {
    int value;
    Private(int value) : value(value) {}
    int getValue() const { return value; }
};

Public* Public::create(int value) {
    return new Private(value);
}

Уменьшается время компиляции и связность, улучшается читаемость и не хочеться несанкционированного доступа
А то что в C++ называется private ниразу не private оно не скрывает реализации просто осложняет доступ.
Зато тянет за собой зависимости связанные с типом приватного поля и его реализацией, чего в большинстве случаев нафиг не упало.
Re[2]: [Trick] Легальный способ доступа к закрытым членам
От: rg45 СССР  
Дата: 27.09.18 13:22
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Поэтому я по возможности предпочитаю такие приватные поля.

_>Уменьшается время компиляции и связность, улучшается читаемость и не хочеться несанкционированного доступа
_>А то что в C++ называется private ниразу не private оно не скрывает реализации просто осложняет доступ.
_>Зато тянет за собой зависимости связанные с типом приватного поля и его реализацией, чего в большинстве случаев нафиг не упало.

Я, в общем-то, имею кое-какое представление и о способах сокрытия реализации, и об абстракных классах, их преимуществах и областях применимости. Я только не очень понимаю, зачем ты здесь обо всем этом рассказываешь. Вопрос, по-моему, был поставлен предельно ясно: "кто хочет вишенку, вот вам вишенка". Кто не хочет, тот не ест. Ты же, зачем-то, пытаешься убедить всех, что хотеть вишенку — это плохо, потому, что на Луне вишни не растут
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 27.09.2018 13:28 rg45 . Предыдущая версия . Еще …
Отредактировано 27.09.2018 13:22 rg45 . Предыдущая версия .
Re[3]: [Trick] Легальный способ доступа к закрытым членам
От: kov_serg Россия  
Дата: 27.09.18 14:05
Оценка:
Здравствуйте, rg45, Вы писали:

R>Я, в общем-то, имею кое-какое представление и о способах сокрытия реализации, и об абстракных классах, их преимуществах и областях применимости. Я только не очень понимаю, зачем ты здесь обо всем этом рассказываешь. Вопрос, по-моему, был поставлен предельно ясно: "кто хочет вишенку, вот вам вишенка". Кто не хочет, тот не ест. Ты же, зачем-то, пытаешься убедить всех, что хотеть вишенку — это плохо, потому, что на Луне вишни не растут

Просто так, не обижайся. Я знаю что у вас черный пояс по C++, но нафига такие вишни? Как вообще пришла в голову идея лезть туда где написано не лезь убъёт?
И что мешало просто вместо private просто написать public или define-ом (#define RELEASE_PRIVATE public) это сделать.
Более того что сечас легальный способ в новом стандарте может перейти в разряд UB, что бы не было скучно.
Re[2]: [Trick] Легальный способ доступа к закрытым членам
От: ViTech  
Дата: 27.09.18 19:34
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Использование decltype в специализации компилируется, но любые попытки использовать дальше не увенчались успехом.

_NN>Есть идеи ?

_NN>
_NN>template<typename MemberPtrT, MemberPtrT>
_NN>struct X;

_NN>template<> struct X<decltype(&A::m_value), &A::m_value> // : Base<decltype(&A::m_value)> // нет доступа
_NN>{
_NN>    // using Q = decltype(&A::m_value);  // нет доступа
_NN>};
_NN>


Может как-то так:
template <class MemberPtr_>
struct Y
{
    using MemberPtr = MemberPtr_;
};

template <class MemberPtr>
struct X;

template <>
struct X<Y<decltype(&A::m_value)>>
{};


template <class MemberPtr>
struct X;

template <class T>
struct K {};

template <class MemberPtr_>
struct K<X<MemberPtr_>>
{
    using MemberPtr = MemberPtr_;
};

template <>
struct X<decltype(&A::m_value)>
{
    using MemberPtr = typename K<X>::MemberPtr;
};
Пока сам не сделаешь...
Re[3]: [Trick] Легальный способ доступа к закрытым членам
От: _NN_ www.nemerleweb.com
Дата: 28.09.18 11:35
Оценка: +1
Здравствуйте, ViTech, Вы писали:

В том и дело, что в специализации можно использовать decltype, но за пределами уже нельзя.
Например тут как добраться до MemberPtr ?
template <>
struct X<decltype(&A::m_value)>
{
    using MemberPtr = typename K<X>::MemberPtr;
};


X<decltype(&A::m_value)::MemberPtr не скомпилируется.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: [Trick] Легальный способ доступа к закрытым членам
От: _NN_ www.nemerleweb.com
Дата: 28.09.18 12:28
Оценка:
Здравствуйте, _NN_, Вы писали:

Объявить дружественную функцию не проблема.
Может есть идеи как протащить дальше?


template <class XX>
typename XX::MemberPtr F(){}


template <class MemberPtr>
struct X;

template <class T>
struct K {};

template <class MemberPtr_>
struct K<X<MemberPtr_>>
{
    using MemberPtr = MemberPtr_;
};

template <>
struct X<decltype(&A::m_value)>
{
    using MemberPtr = typename K<X>::MemberPtr;

    friend X F(); // auto F() -> int (A::*)
};
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: Восстановил (msvc only)
От: rg45 СССР  
Дата: 28.09.18 13:02
Оценка: 9 (1)
Здравствуйте, _NN_, Вы писали:


_NN>Где код ?

_NN>Может получиться что-нибудь придумать.

Вот, восстановил, все-таки. Это работает на mcvc, но не работает на gcc. Ключевой момент в том, что по стандарту хелперная функция не должна в этом случае вноситься в область видимости пространства имен. Но вот на msvc вносится. Э-х-х! сколько клевых фишек можно было бы сделать, будь это стандартым поведением! Синтаксическим сахаром на этор раз не стал особо заморачиваться, ибо все равно не портабельно:

#include <iostream>
#include <utility>

template<typename TagT, typename MemPtrT, MemPtrT memPtr>
struct PrivateMemerAccessEnabler
{
   using PointerToMemberType = MemPtrT;
   static constexpr const PointerToMemberType pointerToMember = memPtr;
   friend PrivateMemerAccessEnabler privateMemerAccessEnablerAux(TagT&&);
};

template <typename TagT>
using PrivateMemberAccess = decltype(privateMemerAccessEnablerAux(std::declval<TagT>()));

#define ENABLE_PRIVATE_MEMBER_ACCESS(TagT, memPtr) \
   template struct PrivateMemerAccessEnabler<struct TagT, decltype(memPtr), memPtr>;

struct A
{
public:

   int GetValue() const { return value; }

private:
   int value = -1;
};

ENABLE_PRIVATE_MEMBER_ACCESS(A_value, &A::value)

int main()
{
   const auto& memPtr = PrivateMemberAccess<A_value>::pointerToMember;

   A a;
   std::cout << a.GetValue() << std::endl;
   a.*memPtr = 42;
   std::cout << a.GetValue() << std::endl;
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.09.2018 13:23 rg45 . Предыдущая версия . Еще …
Отредактировано 28.09.2018 13:21 rg45 . Предыдущая версия .
Отредактировано 28.09.2018 13:15 rg45 . Предыдущая версия .
Отредактировано 28.09.2018 13:10 rg45 . Предыдущая версия .
Отредактировано 28.09.2018 13:05 rg45 . Предыдущая версия .
Re[5]: Восстановил (msvc only)
От: _NN_ www.nemerleweb.com
Дата: 28.09.18 13:22
Оценка: 10 (1)
Здравствуйте, rg45, Вы писали:

2017 в режиме стандартного С++ (/permissive-) не собирает как положено
https://blogs.msdn.microsoft.com/vcblog/2016/11/16/permissive-switch/

error C3861: 'privateMemerAccessEnablerAux': identifier not found
note: 'privateMemerAccessEnablerAux': function was not declared in the template definition context and can be found only via argument-dependent lookup in the instantiation context
note: see reference to alias template instantiation 'PrivateMemberAccess<A_value>' being compiled

http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[6]: Восстановил (msvc only)
От: rg45 СССР  
Дата: 28.09.18 13:25
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>

_NN>error C3861: 'privateMemerAccessEnablerAux': identifier not found
_NN>note: 'privateMemerAccessEnablerAux': function was not declared in the template definition context and can be found only via argument-dependent lookup in the instantiation context
_NN>note: see reference to alias template instantiation 'PrivateMemberAccess<A_value>' being compiled


Ну, ожидаемо, в принципе. Они часто запаздывают, но рано или поздно чинят.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[7]: Восстановил (msvc only)
От: _NN_ www.nemerleweb.com
Дата: 28.09.18 13:34
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Ну, ожидаемо, в принципе. Они часто запаздывают, но рано или поздно чинят.

Где Кодт с решением проблемы ?

Может в reddit подкинуть , глядишь кто-нибудь найдёт лазейку ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: [Trick] Легальный способ доступа к закрытым членам
От: ViTech  
Дата: 28.09.18 13:46
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Здравствуйте, ViTech, Вы писали:


_NN>В том и дело, что в специализации можно использовать decltype, но за пределами уже нельзя.

_NN>Например тут как добраться до MemberPtr ?

Я показал способы, как добраться до decltype(&A::m_value) внутри специализации. Действительно, толку от этого мало, потому как тип нужен снаружи. Надо думать, как отвязаться от зависимости decltype(&A::m_value) в параметре шаблона после того, как доступ к нему получен. Может что-то в таком ключе:
template <>
struct X<SomeTag, decltype(&A::m_value)>
{
    using MemberPtr = typename K<X>::MemberPtr;

    SomeTag::???? ????MemberPtr
    SomeTrait<SomeTag>::???? ????MemberPtr
};

Может в какую-нибудь статическую переменную указатель сохранить (как pointerToMember в начальном примере). Но для этого тоже тип нужен, если только void* не использовать .

_NN>X<decltype(&A::m_value)::MemberPtr не скомпилируется.

И правильно сделается, а то слишком просто было бы .

Во всей этой истории мне интересно, зачем в стандарте такое исключение сделали. Явно не для того, чтоб кто угодно мог по приватным данным шарить. Предполагаю, это нужно, чтобы была возможность специализировать шаблоны для типов из приватных секций(чтоб костыли не писать и в публичных секциях эти типы показывать). Соответственно, пользоваться этими специализациями могут только те, у кого есть доступ к приватным типам, а не все подряд. Есть ещё мысли на этот счёт?
Пока сам не сделаешь...
Re[5]: [Trick] Легальный способ доступа к закрытым членам
От: _NN_ www.nemerleweb.com
Дата: 28.09.18 21:27
Оценка:
Собрав всё воедино получилось вытащить тип и указатель на член наружу.

Получилось вытащить смещение от начала класса, что в принципе должно быть достаточно, чтобы получить доступ.
Осталось понять получить всё во времени компиляции.

Собралось на MSVC.
С GCC надо разбираться
https://ideone.com/nC7H21

Вот набросок.
В Registrator<A>::offset у нас есь смещение A::value.

Буду рад дальнейшим идеям.

#include <iostream>

struct A
{
public:

    int GetValue() const { return value; }

private:
    int q[100];

    int value = -1;
};

template<class MemberPtr, MemberPtr Mem>
struct MemberTag;


template<typename T>
struct MemberOffset
{
    static intptr_t offset;
};

template<typename T>
intptr_t MemberOffset<T>::offset;


template<typename T>
struct Registrator
{
    template<class PointerToMemberType, PointerToMemberType Member>
    struct RegistratorInner
    {
        using TheType = PointerToMemberType;
        static constexpr TheType TheMember = Member;

        struct OffsetInitializer
        {
            OffsetInitializer()
            {
                // Hack to get runtime value of pointer-to-member
                union PtrUnion
                {
                    PointerToMemberType m;
                    intptr_t i;
                };


                PtrUnion p;
                p.m = Member;

                MemberOffset<T>::offset = p.i;
            }
        };

        static OffsetInitializer offsetInitializer;
    };
};


template<typename T>
template<class PointerToMemberType, PointerToMemberType Member>
typename Registrator<T>::template RegistratorInner<PointerToMemberType, Member>::OffsetInitializer
    Registrator<T>::RegistratorInner<PointerToMemberType, Member>::offsetInitializer;


template<class T>
struct MemberExtractor {};

// Extractor specialization
template<class PointerToMemberType, PointerToMemberType Member>
struct MemberExtractor<MemberTag<PointerToMemberType, Member>>
{
    using TheType = PointerToMemberType;
    static constexpr TheType TheMember = Member;
};

// Private member specialization
template<>
struct MemberTag<decltype(&A::value), &A::value>
{
    using TheType = typename MemberExtractor<MemberTag>::TheType;
    static constexpr TheType TheMember = MemberExtractor<MemberTag>::TheMember;

    // Extract type
    virtual typename Registrator<A>::RegistratorInner<TheType, TheMember>::OffsetInitializer F()
    {
        return Registrator<A>::RegistratorInner<TheType, TheMember>::offsetInitializer;
    }
};

template<>
struct MemberTag<decltype(&A::value), &A::value>;

int main()
{
    std::cout << MemberOffset<A>::offset; // 400
}
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[6]: [Trick] Легальный способ доступа к закрытым членам
От: rg45 СССР  
Дата: 29.09.18 08:15
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Собрав всё воедино получилось вытащить тип и указатель на член наружу.


_NN>Получилось вытащить смещение от начала класса, что в принципе должно быть достаточно, чтобы получить доступ.

_NN>Осталось понять получить всё во времени компиляции.

Ну так пока что только смещение и удалось вытащить, ну а что дальше? Чтобы получить-таки доступ к члену, придется использовать принудительное преобразование к нужному типу, который указывать, опять же, придется вручную. Ведь decltype(&A::value) здесь снова не прокатит. Или я чего-то не понял?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[7]: [Trick] Легальный способ доступа к закрытым членам
От: _NN_ www.nemerleweb.com
Дата: 29.09.18 08:23
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну так пока что только смещение и удалось вытащить, ну а что дальше? Чтобы получить-таки доступ к члену, придется использовать принудительное преобразование к нужному типу, который указывать, опять же, придется вручную. Ведь decltype(&A::value) здесь снова не прокатит. Или я чего-то не понял?


Что-то я поспешил. Надо думать.
Кстати код собирается в GCC , не хватало --std==c++17.
https://coliru.stacked-crooked.com/a/facb672f1ff2cd5a
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[5]: Восстановил (msvc only)
От: _NN_ www.nemerleweb.com
Дата: 29.09.18 16:37
Оценка:
Здравствуйте, rg45, Вы писали:

Возможно можно обыграть такую инъекцию дружественной функции.

http://coliru.stacked-crooked.com/view?id=24844806b98f3a0aa2a2be755bade6be-f674c1a6d04c632b71a62362c0ccfc51
http://rsdn.nemerleweb.com
http://nemerleweb.com
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.