Захотелось, наконец-то (!), попробовать variadic templates, так как до сих пор привязан к 10й студии, где ничего этого нету. А чтобы долго не думать, где же можно бесполезно использовать variadic templates, пришла идея попробовать, как будет выглядеть Typelist. Для тех, кто ещё не знает, что это такое, я постараюсь объяснять по ходу дела, а тем, кому это скучно — может сразу пролистать вниз — попробуем написать подобие крестиков-ноликов с использованием Typelist.Итак, TypeList: TypeList
namespace internal {
struct Void
{
};
} // internal
template
struct TypeList
{
typedef H Head;
typedef TypeList Tail;
};
Типичный TypeList представляет собой «голову»(Head) и «хвост»(Tail), который в свою очередь также является списком типов. Использование:
typedef TypeList floating_point_types;
Раньше, без С++11, это выглядело так: Старый TypeList
template
struct typelist
{
typedef H head;
typedef T tail;
};
typedef typelist > floating_point_types;
И макросы в помощь:
#define TYPELIST_1(T1) typelist
#define TYPELIST_2(T1, T2) typelist
#define TYPELIST_3(T1, T2, T3) typelist
…
#define TYPELIST_50…
Но теперь, благодаря variadic templates, можно избавиться и от макросов, и от ограничения на количество типов в списке.Собственно говоря интересным является то, как работать со списком типов, как определить операции над ним и что это даёт в конечном итоге (кому интересно более детальное описание и кто ещё не видел Modern C++ Design — советую почитать — не важно, что это 2001 год!).Итак, как видно, я определил вспомогательный тип internal: Void, который будет работать, как сигнальный флажок и говорить, что список типов пуст (как минимум, для случая, когда пользователь не указал ничего: TypeList<>, или, когда со списка удалено все элементы). Начнём с начала: IsEmpty
IsEmpty
template
struct IsEmpty:
std: true_type
{
};
template<>
struct IsEmpty>:
std: true_type
{
};
template
struct IsEmpty>:
std: integral_constant:: Head, internal: Void>:: value &&
IsEmpty:: Tail>:: value>
{
};
Здесь видно почти всё, что нам нужно, для определения других операций. Как видно, сначала мы определяем «костяк»: тип IsEmpty параметризован одним типом. По сути, это «функция», принимающая один аргумент. Поскольку тип TL означает — «любой тип», мы делаем полную специализацию шаблона для случая с пустым списком: TypeList(можно было бы и просто TypeList<> или, как раз для этого, я определил тип EmptyTypeList) и частичную специализацию, которая работает — «для любого списка типов». Таким образом, наша «функция» определена только для списка типов. В новом стандарте появились такие удобные штуки, как std: integral_constant, которые очень сильно упрощают жизнь: в случае с struct IsEmpty: std: true_type, IsEmpty имеет член класса value, ряд typedef-ов и оператор преобразования в bool.Как это использовать ?:
typedef TypeList TL1;
std: cout << std::boolalpha << IsEmpty:: value << " " << IsEmpty() << std::endl;
Пустой ли список мы имеем определяет следующее выражение:
std::is_same:: Head, internal: Void>:: value &&
IsEmpty:: Tail>:: value
дословно — «список пуст, если его голова — это вспомогательный тип, обозначающий void И если его хвост также является пустым списком». Как видно, здесь используется рекурсия, которую, как раз и останавливает, полная специализация шаблона для пустого списка.Дальше: Contains
Contains
template
struct Contains:
std: false_type
{
};
template
struct Contains:
std: false_type
{
};
template
struct Contains>:
std: integral_constant:: Head, T>:: value ||
Contains:: Tail>:: value
>
{
};
Contains определяет есть ли указанный тип T внутри списка типов TL. Использование: Использование:
typedef TypeList TL;
std: cout << std::boolalpha << Contains:: value << " " << Contains>() << std::endl;
Снова же: «если голова списка это наш тип T, то T есть внутри списка, а иначе — посмотреть, есть ли T в хвосте списка».Частичная специализация — мере предосторожности — а вдруг кто-то воспользуется нашим типом internal::Void?Length
Length
template
struct Length:
std: integral_constant
{
};
template
struct Length>:
std: integral_constant>:: value
? 0
: 1 + Length:: Tail>:: value>
{
};
Если список пуст — длина нулевая, а иначе — это единица (потому что присутствует «голова»(Head)) + длина хвоста:
typedef TypeList TL;
std: cout << Length:: value << " " << Length() << std::endl;
TypeAt
template
struct TypeAt
{
typedef internal: Void type;
};
— возвращает тип по индексу, почти, как массив. Реализация — первый заход (меняем тип N на int):
//template
//struct TypeAt>
//{
// typedef typename std: conditional:: Head,
// typename TypeAt:: Tail>:: type>:: type type;
//};
— всё будет прекрасно работать, но! — хотелось бы быть предупреждённым, если указан слишком большой индекс. Можно бы было выкрутиться и с текущей реализацией, но здесь нужно учитывать то, что шаблон должен быть корректно инстанцирован для случая N=-1. Поэтому идём другим путём:
template
struct TypeAt<0, TypeList>
{
typedef typename TypeList:: Head type;
};
template
struct TypeAt>
{
static_assert (N < Length>:: value, «N is too big»);
typedef typename TypeAt:: Tail>:: type type;
};
— голова имеет нулевой индекс, а для других случаев — будем одновременно уменьшать индекс на единицу и «съедать» кусочек хвоста (передвигаемся слева на право), пока не сможем отнять — индекс нулевой, а текущая голова и есть нужный нам тип! Использование:
typedef TypeList TL2;
static_assert (std: is_same:: type, short>:: value, «Something wrong!»);
Вывод списка
operator<<
// Пустой список
std::ostream& operator<<(std::ostream& ostr, EmptyTypeList)
{
ostr << "{}";
return ostr;
}
template
struct Add>
{
typedef TypeList type;
};
При использовании Append со списком типов в первом аргументе происходит «разложение» на составные. Т.е.:
typedef TypeList TL1;
typedef TypeList TL2;
std: cout << TL1() << ", " << TL2() << std::endl;
std::cout << Add:: type () << ", " << Append:: type () << std::endl;
{int}, {char short}{int {char short}}, {int char short}В первом случае длина результата — 2, тогда как во втором — 3, так как добавляемый список типов «разложился» на компоненты.RemoveAll
Удаление элементов
template
struct RemoveAll
{
};
template
struct RemoveAll>
{
private:
typedef typename RemoveAll:: Tail>:: type Removed;
typedef typename TypeList:: Head Head;
public:
typedef typename std: conditional<
std::is_same:: value,
Removed,
typename Append>:: type
>:: type type;
};
template
struct RemoveAll
{
typedef EmptyTypeList type;
};
Удаление делается так: С пустого списка мы ничего не можем удалить
Если у нас список с одним элементом (только голова) — то вернуть пустой список, если тип головы совпадает с заданным или ничего не изменять в противном случае
Для всех остальных случаев — удалить элементы с хвоста и если тип головы не совпадает с заданным типом — добавить её до результата удаления
Важно то, что, поскольку при удалении с хвоста мы сгрупировали результат в другой список типов, при объединении, используется Append, который «раскручивает» назад сгруппированный список типов.Использование:
typedef TypeList TL;
std: cout << TL() << std::endl;
std::cout << RemoveAll:: type () << std::endl;
{double float float double int char char int char}{double float float double int int}Можно написать ещё одну версию RemoveAll, которая будет удалять со второго списка типов все те, которые есть в первом. Но! В таком случае ее нельзя использовать для списков, которые содержат другие списки:
template
struct RemoveDuplicates>
{
private:
typedef TypeList TL;
typedef typename RemoveAll:: type HeadRemovedFromTail;
typedef typename RemoveDuplicates:: type TailWithoutDuplicates;
public:
typedef typename Append>:: type type;
};
Функция, которая удаляет дубликаты: С пустого списка мы ничего не можем удалить
Удаляем такие же элементы, как и голова из хвоста
Рекурсивно вызываем функцию для хвоста
Объединяем голову с результатом
Пример:
typedef TypeList TL;
std: cout << TL() << std::endl;
std::cout << RemoveDuplicates:: type () << std::endl;
{double float float double int char char int char}{double float int char}Find
Позиция типа в списке
struct Constants
{
typedef std::integral_constant npos;
};
template
struct Find>:
std: integral_constant>:: value
? internal: FindHelper>:: value
: Constants: npos: value>
{
};
Несколько вещей: — Constants — для констант. В нашем случае только для константы, которая говорит о том, что элемент не найден (constexp не поддерживается в моей студии, поэтому UINT_MAX)— internal: FindHelper — собственно говоря, «штука», которая ищет тип в списке, который точно (!) этот тип содержит (дополнительный параметр IndexFrom — начальное значение отсчёта, совсем не нужная вещь:) — рассчитана на случай, когда нужно будет задавать с какой позиции начинать поиск)
Снова же — ничего замысловатого — если указанный тип и тип головы списка совпадает — тогда индекс — нулевой, а иначе — переместится вправо на 1цу и сделать то же самое для хвоста списка.Пример:
template
struct CutFrom>
{
private:
typedef TypeList TL;
public:
typedef typename Slice:: value — 1, TL>:: type type;
};
«Вырезает» указанную часть списка: С пустого списка мы ничего не можем взять
Когда указанные начало (IndexBegin) и конец (IndexEnd) совпадают, то это аналогично операции TypeAt
Начиная с конца указанного диапазона, взять элемент и добавить к результату рекурсивного вызова (в котором конец указанного диапазона уменьшается на 1цу)
Пример:
typedef TypeList TL;
std: cout << TL() << std::endl;
std::cout << Slice<2, 6, TL>:: type () << std::endl;
std::cout << CutTo<2, TL>:: type () << std::endl;
std::cout << CutFrom<8, TL>:: type () << std::endl;
{double float float double int char char int char}{float double int char char}{double float float}{char}Replace
Replace
template
struct Replace
{
};
public:
typedef typename std: conditional<
AtEndWorkAround::value,
typename internal::ReplaceEnd:: type,
typename internal: ReplaceMiddle:: type
>:: type type;
};
Заменяет тип в указанной позиции (Index) на другой (NewValue): Замена первого элемента: Это Head
Cоздать с NewValue список типов к которому добавить «хвост»(т.е. всё кроме первого элемента — «головы»)
Замена последнего элемента: Последний элемент имеет индекс Index — 1
«Вырезать» список от начала и до последнего элемента, не включая его (т.е. до Index — 2)
К результату добавить NewValue
Остальные случаи: Взять список от 0 до Index — 1 — Begin
Взять список от Index + 1 до конца — End
Объеденить три части: Begin + NewValue + End
Пример:
// Will replace first founded @OldValue
template
struct ReplaceType
{
};
template
struct ReplaceType>
{
private:
typedef TypeList TL;
typedef std: integral_constant:: value == Constants: npos: value
> NotFound;
public:
typedef typename std: conditional<
NotFound::value,
TL,
typename internal::ReplaceTypeHelper<
NotFound::value,
OldValue,
NewValue,
TL
>:: type
>:: type type;
};
Аналогично Replace, только сначала нужно найти позицию (Find)
// RepeatT создаёт список типов,
// который состоит из N элементов типа T
typedef RepeatT:: type Row;
typedef RepeatT:: type Field;
Т.е. поле — это список рядков (Важно *), а рядок — это список ячеек.Получается, что-то такое: Пример полей
std: cout << Field() << std::endl;
{
{struct E struct E struct E}
{struct E struct E struct E}
{struct E struct E struct E}
}
И, когда COLUMNS = 4:
{
{struct E struct E struct E struct E}
{struct E struct E struct E struct E}
{struct E struct E struct E struct E}
}
Определим функции для изменения состояния:
Изменение состояния поля
template
struct FigureAt
{
private:
typedef typename TypeAt:: type CurrentRow;
public:
typedef typename TypeAt:: type type;
};
template
struct ReplaceAt
{
private:
typedef typename TypeAt:: type OldRow;
typedef typename Replace:: type NewRow;
public:
typedef typename Replace:: type type;
};
Например:
Как это выглядит
typedef ReplaceAt<1, 1, X, Field>:: type Field2;
typedef ReplaceAt<2, 1, X, Field2>:: type Field3;
{
{struct E struct E struct E struct E}
{struct E struct X struct E struct E}
{struct E struct X struct E struct E}
}
Поле есть, выставлять значения мы умеем — нужно как-то определять есть ли победитель? Для начала — простой случай: победил тот, кто выстроил или ряд, или столбик (по диагонали не будем считать сейчас). Т.е. на входе у насть есть масив рядочков (столбиков) и если среди них присутствует хотя бы один, полность выстроенный одним знаком (крестиком, ноликом) рядочек (столбик), то этот знак победил! Итак:
// Если есть хотя бы один рядок или столбик победителя
static const bool value = Contains:: value ||
// Field — это список рядков, ReconfigureField -
// превращает список рядков в список столбиков
Contains:: type>:: value;
};
Взглянем на ReconfigureField:
template
struct ReconfigureField
{
typedef typename internal: ReconfigureFieldHelper<
COLUMNS - 1, EmptyTypeList, F>:: type type;
};
Что ReconfigureField делает на практике (это хорошо видно, когда количество столбиков и рядочков неодинаково):
ReconfigureField: результат
typedef ReplaceAt<1, 1, X, Field>:: type Field2;
typedef ReplaceAt<2, 1, X, Field2>:: type Field3;