В недавно вышедшей Boost 1.66 beta 1 присутствует новая библиотека метапрограммирования Mp11. Я начал читать отсюда и обратил внимание на два примера.
Первый — конкатенация произвольного набора кортежей типов. Мне показалось очень сложным представленное решение Эрика Ниблера, поскольку «на глаз» было видно, что стандартными средствами это сделать довольно легко, ЕСЛИ ограничиться поддержкой std::tuple. Естественно, я попробовал написать свою версию, которую и привожу ниже.
Вначале конкатенация двух tuple:
template <class A, class B> struct Tcat; template <class... T, class... S> struct Tcat<std::tuple<T...>, std::tuple<S...>> { using type = std::tuple<T..., S...>; }; template <class A, class B> using tcat = typename Tcat<A, B>::type;
Теперь не составит труда определить конкатенацию произвольного количества tuple рекурсивно:
template <class... A> struct Tcats; template <class... T> struct Tcats<std::tuple<T...>> { using type = std::tuple<T...>; }; template <class A, class B, class... Other> struct Tcats<A, B, Other...> { using type = typename Tcats<tcat<A, B>, Other...>::type; }; template <class... T> using tcats = typename Tcats<T...>::type;
Второй — transform бинарной операцией двух кортежей (zip, если в терминах функциональных языков программирования). Конкретный случай для двух кортежей не столь интересен. Легко видеть, что при обобщении на произвольное число кортежей мы получаем ни что иное как транспозицию матрицы (F — применяемая операция):
<T11, T12, T13, ..., T1m>,
<T21, T22, T23, ..., T2m>,
...
<Tn1, Tn2, Tn3, ..., Tnm>
--->
F<T11, T21, ..., Tn1>,
F<T12, T22, ..., Tn2>,
F<T13, T23, ..., Tn3>,
...
F<T1m, T2m, ..., Tnm>
Насколько просто сделать такую штуку на шаблонах C++? Результат, который у меня получился, мне не особенно понравился. Думаю, можно это сделать лучше, однако, не факт, что это «лучше» будет означать «короче».
Итак, метафункция, порождающая из столбца исходной матрицы строку новой матрицы с префиксом F (n-арной операцией):
template <class I, template <class...> class F, class... T> struct Tcol { using type = F<std::tuple_element_t<I::value, T>...>; }; template <class I, template <class...> class F, class... T> using tcol = typename Tcol<I, F, T...>::type;
Здесь I — тип, определяющий зависимое имя value, раскрываемое в целочисленную константу — индекс столбца.
Теперь надо применить tcol к диапазону индексов столбцов. Так как готовых iota, map и bind у меня нет, я написал рекурсивную реализацию аналога «for» «в лоб»:
template <class S, class T, template <class...> class F, class... Other> struct Tfor; template <class Int, Int s, template <class...> class F, class... Other> struct Tfor< std::integral_constant<Int, s>, std::integral_constant<Int, s>, F, Other...> { using type = std::tuple<>; }; template <class Int, Int s, Int t, template <class...> class F, class... Other> struct Tfor< std::integral_constant<Int, s>, std::integral_constant<Int, t>, F, Other...> { static_assert(s < t, ""); using S = std::integral_constant<Int, s>; using S1 = std::integral_constant<Int, s+1>; using T = std::integral_constant<Int, t>; using type = tcat<std::tuple<F<S, Other...>>, typename Tfor<S1, T, F, Other...>::type>; }; template <class S, class T, template <class...> class F, class... Other> using tfor = typename Tfor<S, T, F, Other...>::type;
Здесь S и T — типы, задающие начало и конец диапазона (::value — соответствующие целочисленные константы), пак Other — исходная матрица типов.
Теперь записать обобщённый zip не составит труда:
template < template <class...> class F, class... T> struct Tzip; template < template <class...> class F> struct Tzip<F> { using type = std::tuple<>; }; template < template <class...> class F, class T0, class... T> struct Tzip<F, T0, T...> { template <class I, class... Y> using tcolF = tcol<I, F, Y...>; using type = tfor< std::integral_constant<std::size_t, 0>, std::integral_constant<std::size_t, std::tuple_size<T0>::value>, tcolF, T0, T...>; }; template < template <class...> class F, class... T> using tzip = typename Tzip<F, T...>::type;
Пришлось вынуть первый кортеж (строку матрицы) — T0, чтобы узнать количество столбцов. Представляю, какое веселье будет в выводе компилятора, если скормить последовательность кортежей разных размеров :)
template <class... T> struct Tmax; template <class T> struct Tmax<T> { using type = T; }; template <class T0, class... T> struct Tmax<T0, T...> { using tail_max = typename Tmax<T...>::type; using type = std::conditional_t< (T0::value < tail_max::value), tail_max, T0>; }; template <class... T> using tmax = typename Tmax<T...>::type; template <class T> using Sizeof = std::integral_constant<std::size_t, sizeof(T)>; template <class... T> using tmax_sizeof = tmax<Sizeof<T>...>; int main() { using namespace std; // Concat. { using t0 = tuple<int, int*>; using t1 = tuple<float>; using t2 = tuple<char*, char**, void*, void**>; using t3 = tuple<>; using ex = tuple< int, int*, float, char*, char**, void*, void**>; static_assert(is_same<tcats<t0, t1, t2, t3>, ex>::value); } // Generic zip. { using r0 = tuple<char, short, int, long>; using r1 = tuple<unsigned, void*, void**, void***>; using exr = tuple< tuple<char, unsigned>, tuple<short, void*>, tuple<int, void**>, tuple<long, void***>>; //print_t(tzip<tuple, r0, r1>{}); static_assert(is_same_v<exr, tzip<tuple, r0, r1>>); using t0 = tuple<int[11], int[12], int[13]>; using t1 = tuple<int[21], int[22], int[23]>; using t2 = tuple<int[31], int[32], int[33]>; using t3 = tuple<int[41], int[42], int[43]>; using ext = tuple< tuple<int[11], int[21], int[31], int[41]>, tuple<int[12], int[22], int[32], int[42]>, tuple<int[13], int[23], int[33], int[43]>>; static_assert( is_same_v<ext, tzip<tuple, t0, t1, t2, t3>>); using exsz = tuple< Sizeof<int[41]>, Sizeof<int[42]>, Sizeof<int[43]>>; static_assert( is_same_v<exsz, tzip<tmax_sizeof, t0, t1, t2, t3>>); } }