You are viewing [info]panano's journal

CRTP

  • Apr. 26th, 2012 at 7:10 PM
oblolo
Есть такой фокус в плюсах - шаблон CRTP, он же статический полиморфизм. Основной вопрос новичков, столкнувшихся с этим взъёбом ума, это "нахуя??". Есть масса ответов, но я представлю ещё один - это реализация графа с помощью наследования от рекурсивно параметризованного стандартного контейнера. Сначала код:

template <typename T>
class Node: public vector< Node<T> > {
    T data;
public:
    Node(T _data): data(_data) {}
    T & operator*() { return data; }

    template <typename F>
    void for_each(F f) {
        for( size_t i = 0; i < this->size(); ++i )
            (*this)[i].for_each(f);
        f(data);
    }
};

Здесь:
  1. Node - шаблонный класс, представляющий вершину, параметризуется данными этой вершины.
  2. Фокус, о котором речь: vector< Node<T> > - базовый класс, представляющий собой вектор вершин.
  3. Конструктор и оператор разыменования.
  4. Функция обхода в глубину.
Собственно и всё!
Всю основную функциональность по выделению-освобождению памяти, навигации и прочему берет на себя вектор!
Вектор также можно заменить на параметр шаблона, но толку в общем с гулькин хуй, а лаконичность теряется.

Как этим пользоваться:

    typedef Node<size_t> Ns;

    Ns n(10);                   // объявляем вершину
    n.push_back(20);            // добавляем ей детку
    n.push_back(25);            // ещё одну
    n[0].push_back(30);         // теперь добавляем внучка
    n[0].push_back(40);         // и ещё одного

    n[0][1] = 50;               // можно редактировать данные вершины вот так

    cout << "N " << *n << "\n";        // можно получать данные вершины вот так
    cout << "N " << *n[0] << "\n";     // а вот так данные детки
    cout << "N " << *n[0][1] << "\n";  // а вот так данные внучка

    n.for_each( cout << var("Node: ") << _1 << "\n" );  // а вот так можно обойти всех зараз
                 // (include boost::lambda, если чо)


Выхлоп у этой конструкции вот такой:
N 10
N 20
N 50
Node: 30
Node: 50
Node: 20
Node: 25
Node: 10

Пиздато, не правда ли?

А теперь для снятия интеллектуального напряжения предлагается



Apr. 25th, 2012

  • 6:15 PM
oblolo

Как определить в С++, имеет ли заданный тип определённый operator()?
Ответ настолько полон трюками, что внутренний Александреску пробудился и пишет

Сначала ответ:

template <typename Type>
class has_member
{
   class yes { char m;};
   class no { yes m[2];};
   struct BaseMixin    {  
    void operator()(){}
   };
   struct Base : public Type, public BaseMixin {};
   template <typename T, T t>  class Helper{};
   template <typename U>
   static no deduce(U*, Helper<void (BaseMixin::*)(), &U::operator()>* = 0);
   static yes deduce(...);
public:
   static const bool result = sizeof(yes) == sizeof(deduce((Base*)(0)));
};



Рассмотрим, как это работает.

1. Здесь определены два типа. Содержимое их значения не имеет. Имеют значение лишь их размеры. Размеры должны быть различными, неважно, кто больше, но различными. Почему - см. ниже.
   class yes { char m;};
   class no { yes m[2];};


2.Здесь определена первая структура-помощник. Единственное, что с ней можно сделать - это вызвать её operator(). Зачем она нужна - см. ниже.
   struct BaseMixin    {  

3. Вторая структура-помощник. Она является порождением нашего входного типа Type и первого помощника.
   struct Base : public Type, public BaseMixin {};

Её интересной особенностью является то, что:
1. если у Type нет оператора(), то у Base он однозначно определён - потому что он есть у BaseMixin
2. если же у Type есть оператор(), то у Base он определён уже неоднозначно - потому что происходит путаница между определением оператора() в Type и в BaseMixin. Этим мы воспользуемся чуть позже

4. Третья структура помощник. Не умеет вообще ничего. Но является шаблоном с секретом
   template <typename T, T t>  class Helper{};

Вот в чём её секрет: у шаблона два параметра, которые связаны зависимостью: первый должен являться типом второго (или что тоже самое - второй должен являться объектом класса, указанного в первом параметре)

5. Кульминация:
   template <typename U>
   static no deduce(U*, Helper<void (BaseMixin::*)(), &U::operator()>* = 0);


Что здесь такое. Это шаблонная функция, принимающая два аргумента:
U* - указатель на некий тип
Helper<void (BaseMixin::*)(), &U::operator()>* - указатель на структуру Helper, определённую в п.4.

Структура Helper, как мы помним, может быть инстанцирована только если два её шаблонных параметра связаны отношением is-a (т.е. второй является объектом первого). Рассмотрим детально наши параметры.

5.1. void (BaseMixin::*)() - это указатель на функцию-член класса BaseMixin. (Функция ещё и без параметров должна быть, но здесь это неважно)

5.2. &U::operator() - а это уже указатель на перегруженный operator() класса U.

В результате выходит так:
если U::operator() является членом класса BaseMixin, то Helper успешно создаётся, а вслед за ним успешно создаётся и функция deduce.

В противном же случае.. не, не ошибка. Происходит нечто, что лучше всего характеризуется аббревиатурой SFINAE. Детали легко гуглятся, но если коротко - то компилятор ищет ещё какой-нибудь deduce. И находит:

6. Вот он, deduce на все случаи жизни. Между скобок у него многоточие, оно же элипсис, что значит, что он съест любые параметры. (За всеядность компилятор даёт ему наинизший приоритет, что важно).
   static yes deduce(...);

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

И, наконец, развязка:

7.
  static const bool result = sizeof(yes) == sizeof(deduce((Base*)(0)));

Здесь много скобок, поэтому пойдём из самого нутри:
(Base*)(0) - это обычный ноль, но приведённый к указателю на класс Base

deduce((Base*)(0)) - увидев это, компилятор пытается инстанцировать deduce для типа Base*

Он смотрит на определение из п.5:
   template <typename U>
   static no deduce(U*, Helper<void (BaseMixin::*)(), &U::operator()>* = 0);


всемто U у нас оказывается Base, и, соответственно реализация функции выглядит так:

static no deduce(Base*, Helper<void (BaseMixin::*)(), &Base::operator()>* = 0);

Второй параметр имеет значение по умолчанию 0, поэтому его отсутствие не смущает компилятор.
Интереснее другое. Вот это:

&Base::operator()

Если вспомнить п.3, то это выражение имеет однозначный смысл только если у класса Type нет своего operator(). Т.е. Base::operator() эквивалентно BaseMixin::operator().

В таком случае условие создания структуры Helper выполняется (т.к. указатель на BaseMixin::operator() является указателем на функцию-член BaseMixin), структура Helper успешно <s>создаётся</s> реализуется, а следом за ней реализуется и функция deduce.

Если же у Type есть оператор(), то компилятору приходится реализовывать второй, всеядный deduce из п.6.

Ещё на одну скобку вверх:

sizeof(deduce((Base*)(0)))

Здесь мы должны получить размер возвращаемого функцией deduce значения. Фокус в том, что компилятор, как и читатель программы, способен узнать этот размер, не запуская эту функцию. Он просто смотрит на возвращаемый тип в сигнатуре функции да и считает его размер.


Вот и всё. В наличии следующее явление.
Если у Type нет оператора(), то тип deduce = no, а если есть - то yes.
Размеры у них тоже разные.

Сравниваем и возвращаем.

struct A { void operator() (int ) {}; };
struct B { int i; };

cout << "A " << has_member<A>::result << "\n";
cout << "B " << has_member<B>::result << "\n";


Получаем
А 1
B 0


Метода подходит и для определения наличия других членов, и других операторов. Мощная техника, в общем. Можно заdefinеить требуемый элемент и вызывать проверку для кого угодно.

Правда, можно нарваться на всякое типа "operator[] должен иметь ровно один аргумент", но это преодолимая.



Преодолимая хуйня.
Функциональная схема №1:

Apr. 25th, 2012

  • 4:55 PM
oblolo
Ещё в С++ запрещено использовать локальные типы при инстанцировании шаблона.

т.е. вот:

template <typename T> struct faka {
   typedef T fakatype;
};

struct globalfaka {};

void main()
{
     struct localfaka {};

     faka<globalfaka> f1;    // так можно
     faka<localfaka> f2;     // а так нельзя
}



Причин этому нетЪ.

Apr. 25th, 2012

  • 2:06 PM
oblolo
В С++ запрещено переопределять operator= вне объявления класса.

Почему?
Во избежание.
operator= для любого класса всегда есть, независимо от того, определён ли он программистом. Если не определён - компилятор генерирует дефолтный.

Посему,  во избежание неоднозначностей типа вот этой:

struct A {
    int i;
    A(int _i): i(_i) {};
}

A a1(100), a2(666);

a2 = a1;     // теперь a2.i равно 100

A& operator=( A& dst, A& src) {
   dst.i = src.i + 1;
}

a2 = a1;      // теперь a2.i равно 101














Нельзя. Хотя по-моему с ним было бы пиздаче.

Apr. 25th, 2012

  • 1:57 PM
oblolo

В С++ запрещено задавать для шаблонной функции шаблонные параметры по умолчанию.

Т.е. нельзя написать:
template <class A, class B = void*> bool f( A a, B b = 0) { blablabla; }

Если было бы можно, то вызов f(1) сгенерировал бы функцию
bool f(int a, void * b = 0)


При этом аналогичный шаблон класса создать можно.

Штрауструп говорит, что если приспичило, то надо писать так:
template <class A, class B> bool f( A a, B b )        { blablabla; }
template <class A>          bool f( A a, void *b = 0) { blablabla; }



Проблема в том, что blablabla в результате дублируется

Штраус сам признаёт:
The prohibition of default template arguments for function templates is a misbegotten remnant...
The restriction seriously cramps programming style...

Mar. 27th, 2012

  • 1:21 PM
oblolo
Получаешь всегда меньше, чем хочешь. Поэтому хотеть надо больше, чем нужно.

Mar. 16th, 2012

  • 5:35 PM
oblolo

Nolwenn Leroy


охуенна

Mar. 15th, 2012

  • 2:05 PM

Mar. 15th, 2012

  • 12:36 PM
oblolo

Emily Loizeau

охуенна

Mar. 7th, 2012

  • 10:22 AM
oblolo
События последний дней открыли глаза русского народа. Срыв Площади Революции и пересадка на Чеховскую разбередили затянувшуюся рану на теле русского народа. Запоздалые протесты Зюганова почесали нос русского народа. Слёзы Путина вызвали тик века русского народа. Поддержка ОБСЕ свела судорогой ноги русского народа. Молчание Никиты Михалкова запустило руки русского народа в трусы русского народа. Потоки славословия вызвали судорожный зевок русского народа. Притеснение русского народа больно ударило по мудям русского народа. Спущенная в очко покосившегося, замшелого сортира русского народа государственность русского народа обнажила головку члена русского народа. Откат к советской эпохи скрыл под складками кожи головку члена русского народа. Старые новые обещания обнажили головку члена русского народа. Угроза гражданской войны скрыла головку члена русского народа. Карнавальная радость кровавой резни обнажила головку члена русского народа. Страх ночных воронков скрыл головку члена русского народа. Чёрножопые полчища Рамзана обнажили головку члена русского народа. Слюнявые отары Кургиняна скрыли головку члена русского народа. Гордость за достижения предков обнажила головку члена русского народа. Сталин скрыл головку члена русского народа. Гагарин обнажил головку члена русского народа. Опричнина вскрыла русского народа. Водянова обнажила русского народа. Газета правда скрыла русского. Воля народа обнажила русского. Воля народа показала русского. Воля народа восстала русского.

Profile

oblolo
[info]panano
chabakku

Latest Month

April 2012
S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     

Syndicate

RSS Atom
Powered by LiveJournal.com
Designed by Tiffany Chow