лучше отложить.
Если какие-то начальные представления о C++ есть — то советую сначала прочитать The C++ Programming Language Страуструпа, если же нет то тогда Programming Principles and Practice Using C++.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, jyuyjiyuijyu, Вы писали:
J>>сабж... J>>я совсем потерянный С++'ник ?
EP>Смысла "читать трехэтажные шаблоны" не понимая как работает перегрузка, ADL
лучше отложить. EP>Если какие-то начальные представления о C++ есть — то советую сначала прочитать The C++ Programming Language Страуструпа, если же нет то тогда Programming Principles and Practice Using C++.
ну как бы у меня знания далеко за начальными... но шаблоны мне как то недаются..
лучше отложить. EP>Если какие-то начальные представления о C++ есть — то советую сначала прочитать The C++ Programming Language Страуструпа, если же нет то тогда Programming Principles and Practice Using C++.
О да, я эту навернутую конструкцию тоже не понял. Обхожусь как-то без таких наворотов. Кстати, может прокомментируете построчно зачем она нужна такая?
Нормальные шаблоны — это в моем понимании например такое
template<class T, int N>
class Array {
T data[N];
public:
T& operator[](int i);
};
ну может в реальных проектах методов будет побольше, но все равно идея понятна. Все конкретно. А вот что делает полезного абстрактная конструкция, приведенная топикстартером — ума не приложу.
Здравствуйте, jyuyjiyuijyu, Вы писали:
J>ну как бы у меня знания далеко за начальными... но шаблоны мне как то недаются..
Вспомни: поначалу сам язык кажется магией — все эти приоритеты операций, отличия между указателями, ссылками, уж не говоря про конструкции вида ->*
Потом это [ос|ус]ваивается, но теперь уже смотрим на шаблоны, которые, кроме простейших "template<typename T> T add(T x, T y)", также выызвают непонимание, а уж что говорить про труд Александреску, который кажется вообще за гранью фантастики.
Дальше — больше: буст, лямбды, && и std::forward, etc...
Здравствуйте, include2h, Вы писали:
I>О да, я эту навернутую конструкцию тоже не понял. Обхожусь как-то без таких наворотов.
Людям и C и Pascal хватало Такую blub риторику — в топку.
I>Кстати, может прокомментируете построчно зачем она нужна такая?
Пожалуйста, здесь всё подробно расписал.
I>Нормальные шаблоны — это в моем понимании например такое I>
template<class T, int N>
I> class Array {
I> T data[N];
I> public:
I> T& operator[](int i);
I> };
I> ну может в реальных проектах методов будет побольше, но все равно идея понятна. Все конкретно. А вот что делает полезного абстрактная конструкция, приведенная топикстартером — ума не приложу.
Например есть структура, нужно сделать массив этих структур, причём известно что паттерны использования такие, что использование структуры массивов было бы выгоднее. Один из вариантов решения — Boost.Fusion + подобные конструкции.
Использовать или нет такие конструкции, естественно зависит от ситуации в конкретном проекте
— нет. J>>ну как бы у меня знания далеко за начальными... но шаблоны мне как то недаются..
EP>ОК. EP>Есть namespace math, в нём обычный класс dcomplex. Куда поместить функцию cos? EP>ответ
Конечно, синтаксис и практика применения шаблонов С++ громоздка от безысходности, но, в принципе, не так уж и мрачна — если вкурить в идиомы, которыми народ пользуется. J>я об этом
Если ты конкретно об этом, то давай попробуем вместе разобраться.
Первое. ЧТО эта конструкция делает?
Ответ очевиден: здесь определены две функции — Arg pfo(Arg) и Arg2 pfo(Arg1,Arg2), где Arg1, Arg2 — произвольные типы.
(Далее, для краткости, я буду называть их pfo, а не PolymorphicFunctionObject).
Второе. Зачем обе они засунуты в один класс?
Это такая идиома: засовывать семейство функций, то есть нечто полиморфное, в класс фиксированного типа.
Семейство может строиться как шаблон функций, как набор перегрузок, или — как здесь — и перегрузка, и шаблоны.
Эта идиома позволяет передавать всё семейство функций как аргумент вызова или как параметр шаблона.
Дело в том, что семейство обычных функций (шаблонных или нет) идентифицируется по имени, а имя как таковое не является первоклассной сущностью. То есть, сказать компилятору имя мы можем и должны, а вот передать и сохранить имя — уже никак. Только указатели или абстрагированные типы — параметры шаблона. В С++ рефлексия никакая.
А тут очень удобно: завернули семейство в тип, и можем передавать и сам тип как параметр шаблона, и его экземпляры как аргументы функций.
Третье. Надеюсь, не надо объяснять, как класс с оператором() синтаксически выглядит, словно функция?
А вот четвёртое — это существенное отличие.
Допустим, мы хотим написать
auto x = pfo(y,z);
// или даже кручеtypedef typeof(pfo(y,z)) X;
X x1, x2; .....
хорошо компилятору, если он, во-первых, поддерживает стандарт C++11 (или, хотя бы, расширения gcc-03). Конечно, даже C++98 компилятор сможет вывести тип результата pfo(y,z) для известных y и z, но пользователю он ничего не позволит с этим выводом сделать.
А потребность узнать тип результата часто встречается. Например, от неё может зависеть тип функции, в которой, собственно, и был сделан вызов pfo.
Для обычных функций можно выполнить разбор типа:
template<class R, class A1, class A2>
shared_ptr<R> // чисто для примера - пусть foo возвращает некоторый тип, производный от искомого
foo( R(*f)(A1,A2) ) // здесь мы выполняем сопоставление
{
return shared_ptr<R>(new f(0,0));
}
// там, где сопоставлять вот так неудобно, можем написать единожды метафункциюtemplate<class F> struct bin_fun_traits;
template<class R, class A1, class A2> struct bin_fun_traits< R(A1,A2) >
{
typedef A1 a1_type;
typedef A2 a2_type;
typedef R result_type;
};
template<class F> struct Foo // ну можно, конечно, написать struct Foo<R(A1,A2)>...
{
typedef typename bin_fun_traits<F>::result_type res_type;
typedef shared_ptr<res_type> ptr_type;
ptr_type p_, q_;
.....
void setup(Foo f)
{
p_ = ptr_type(new f(1,2));
q_ = ptr_type(new f(3,4));
}
.....
};
Для произвольных классов, выдающих себя за функции, — понятное дело, такое сопоставление ничего не даст.
Поэтому разработчики договариваются передавать вместе с классом сопроводительную информацию.
Первая такая конвенция возникла в эпоху достандарта 98 года, первые версии STL. Тогдашние компиляторы были очень глупыми, и приходилось писать
struct multiply
{
typedef int first_argument_type;
typedef int second_argument_type;
typedef int result_type;
int operator()(int x, int y) const { return x*y; }
};
или, что проще, наследоваться от заготовок — std::unary_function<A1,R> и std::binary_function<A1,A2,R>.
Вторая конвенция — когда появился boost::bind, способный принимать всеядные функции — но тип результата ему всё ещё нужен.
struct whatever_less
{
typedef bool type;
template<class X, class Y> bool operator()(X x, Y y) const { return x<y; }
};
boost::function<bool(char)> is_ctrl = boost::bind(whatever_less, _1, 32); // слишком простой пример, но наворачивать что-то реальное я не хочу
Но понятно, что если в класс завёрнуто семейство функций, возвращающих разные типы, то такой фокус не пройдёт.
И вот тогда — внимание, трюк! Даже два трюка!
Напишем метафункцию, которая по набору типов аргументов выдаст искомый тип результата. И засунем эту метафункцию в наш класс.
struct tararam
{
// вот наши функцииint operator()(int x, int y) const { return x-y; }
short operator()(char x, char y) const { return (short)(x+y); }
// а вот метафункция, описывающая ихtemplate<class X, class Y> struct result;
template<> struct result<int,int> { typedef int type; };
template<> struct result<char,char> { typedef short type; };
};
template<class F, class X> void foo(F f, X x, X y)
{
typedef typename F::template result<X,X>::type xx_type;
shared_ptr<xx_type> r (new f(x,y));
.....
}
...
foo(tararam, 'a', 'b');
Хорошо получилось? Почти. Ведь, если у семейства функций разная арность (количество аргументов), то придётся писать семейство метафункций?
struct tararam
{
// вот наши функцииint operator()(int x) const { return -x; }
short operator()(char x, char y) const { return (short)(x+y); }
// а вот метафункция, описывающая ихtemplate<class X> struct result1;
template<> struct result1<int> { typedef int type; };
template<class X, class Y> struct result2;
template<> struct result2<char,char> { typedef short type; };
};
Так можно, но некрасиво. Тут придётся или прибегнуть к variadic templates (шаблонам с произвольным количеством параметров), или как-то упаковывать параметры в один синтетический тип, поддающийся сопоставлению.
Вариадики
Последнее выгодно тем, что
— не тащит лишних зависимостей (от boost/std::tuple или boost::mpl)
— выглядит соответственно вызову: F(X,X) и f(x,x)
Кстати, заметь!
В моих отрывках — сперва идут сами функции, а потом их обвязка метафункцией. Как мне кажется, это упрощает понимание, ставит лошадь впереди телеги.
А в твоём примере телега впереди, и это, конечно, слегка озадачивает: откуда взялась эта метафункция, зачем она вообще?
Но это уже вопросы стиля. Точно так же, как некоторые любят писать private-секцию с членами-данными в начале класса (чтобы был виден его лэяут), а другие — в конце (выставляя на первое место его public интерфейс).
Возможно, что и некоторые другие "трёхэтажные" конструкции станет легче понимать, если мысленно выделять и переставлять смысловые блоки.
EP>>ну math::dcomplex::cos как то логичней меньше конфликтов возможных...
EP>>Каких конфликтов? Что насчёт ADL? Что с удобством? J>ну например сущностей с такими же именами
Какая сигнатура должна быть и где должны быть расположены эти сущности, чтобы наступил конфликт?
J>с ADL я плохо знаком
Есть в TC++PL.
J>с удобством для нешаблонных классов все в порядке
Что удобней:
1. math::dcomplex::cos(value)
2. dcomplex::cos(value) // после using
3. cos(value) // даже без всяких using, при том что cos находится в math
?
У Страуструпа рассматриваются подобные и многие другие моменты. Но что самое главное, он учит хорошему стилю.
Начинать нужно именно с этого, а не с TMP.
Здравствуйте, jyuyjiyuijyu, Вы писали: J>перечитал два раза J>но видимо мой уровень в шаблонах настолько низок что я понимаю Вас с трудом
Могу повторить третий раз и помедленнее
Если коротко, то здесь есть идиомы:
1) запихнуть семейство функций в класс, — чтобы передавать это семейство в другие шаблоны (по имени можно передавать только в макросы, а макросы в сях, сам знаешь, насколько интеллектуальные)
2) чтобы узнать тип, возвращаемый функцией, — нужно или использовать фичи стандарта 2011 (decltype), или метафункцию: специальный шаблон, который по типу аргументов выводит тип результата.
3) чтобы передавать произвольное количество типов аргументов, — нужно или использовать фичи 2011 (вариадики), или упаковывать типы в кортеж.
В качестве кортежа взяли тип сигнатуры функции: doesnt_matter(arg1_type,arg2_type,arg3_type)
Подозреваю, что больше всего напрягает метафункция и её применение.
Ну тут да, есть немножко шаблонной магии
Очень советую прочитать Александреску, а потом забыть его поскорее. Там показано, как эту шаблонную магию готовят, но... в общем, не надо делать, как Александреску сделал в Loki.
Опять же, если вкратце.
Обычные шаблоны как работают? Ты пишешь основной шаблон template<class A, class B> class Foo и, по желанию, его частичные и полные специализации. Затем подсовываешь в угловые скобки нужные тебе параметры, компилятор ищет подходящую специализацию и дальше имеет дело с конкретным классом.
Первая хитрость: сопоставление аргументов может быть затейливым. Мы можем выковыривать базовые типы из производных
template<class> class WhatAboutPointer;
template<class T> class WhatAboutPointer<T*> { .... T .... };
.....
WhatAboutPointer<int*> // в этом классе параметр T принял значение inttemplate<class> class WhatAboutBinaryFunction;
template<class R, class X, class Y> class WhatAboutBinaryFunction< R(X,Y) > { .... R .... X .... Y .... };
.....
typedef int foo_signature(char,short);
WhatAboutBinaryFunction< foo_signature > // выделили из сигнатуры, что она получает и что возвращает
Вторая хитрость: специализации шаблонов — это аналоги перегрузки функций.
int foo(char);
long foo(char,short);
..... foo('a') ..... // вызовем первую сигнатуру, как наиболее подходящуюtemplate<class> struct Foo;
template<class R, class X> struct Foo<R(X)> { ..... }; // первая специализацияtemplate<class R, class X, class Y> struct Foo<R(X,Y)> { ..... }; // вторая
..... Foo<int(char)> ..... // класс получен из первой специализации шаблона
Третья хитрость: иногда нас интересуют объекты — экземпляры класса, полученного по шаблону. Со всеми их данными и методами. Собственно, это классическое применение шаблонов, и все учебники по С++ с этого и начинают.
Но иногда! Нам нужны так называемые зависимые имена — типы и константы, объявленные в шаблоне.
Шаблон класса, который эксплуатируют только ради зависимых имён, принято называть "характеристиками" (traits, трейтс).
Если ты спросишь, а нафига нужно typedef typename AddPointer<int>::type, если можно сразу написать int*, то отвечу.
если ты спросишь; а так — не отвлекайся от хода мысли
1) иногда зависимые типы бывают довольно затейливыми
2) трейтс позволяет не только дописать что-то к параметру (из int сделать int*), но и что-то выдрать (из int* сделать int, например)
3) иногда нужно ввести абстрагирующую прослойку: сегодня и на этой платформе тип будет один, а завтра и на другой — поменяется (условная компиляция, версии программы)
4) при известной ловкости, мы можем подсовывать в один и тот же пользовательский шаблон разные трейтсы.
Четвёртая хитрость:
Трейтс — это аналог объекта. Только если у объекта есть члены-данные, то у трейтса — зависимые имена-типы.
Но, помимо объектов, есть ещё такое явление, как функция. У функции сколько-то аргументов, а возвращает она одно значение.
Поэтому довольно часто используется вырожденный случай трейтса: шаблон класса, который содержит одно только зависимое имя.
Эта штука называется метафункцией, и является аналогией функции.
int foo(int x) { return x+1; }
template<int X> struct F { static constvalue = X+1; }; // метафункция над числамиtemplate<class T> struct P { typedef T* type; }; // метафункция над типами
Теперь собираем все четыре хитрости вместе
Как писать метафункции, и как применять их — это тема для отдельного разговора.
Но, по крайней мере, надеюсь, что ты станешь узнавать трейтсы и метафункции, и это не будет больше горой непонятных угловых скобочек, понапиханных неизвестно зачем.
Здравствуйте, jyuyjiyuijyu, Вы писали:
J>перечитал два раза J>но видимо мой уровень в шаблонах настолько низок что я понимаю Вас с трудом
Шаблоны (в смысле template metaprogramming) сходны с функциональным программированием. Поэтому тебе может помочь изучение какого-нибудь функционального языка.
Ну а так, чем больше пишешь кода с использованием шаблонов, тем лучше их понимание.
Здравствуйте, alexeiz, Вы писали:
A>Шаблоны (в смысле template metaprogramming) сходны с функциональным программированием. Поэтому тебе может помочь изучение какого-нибудь функционального языка.