Скажем, у меня есть
foo
который принимает одно значение каждого из этих списков в качестве параметра Как я могу создать пакет с переменными параметрами foo
s, параметризованный декартовым произведением двух элементов списка?
Вот что я имею в виду:
template<int ...>
struct u_list {};
template<char ...>
struct c_list {};
template<int, char >
struct foo {};
template<class ...>
struct bar {};
using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;using result_t = /* magic happens*/
using ref_t = bar<
foo<1, -3>, foo<1, 3>,
foo<5, -3>, foo<5, 3>,
foo<7, -3>, foo<7, 3>
>;
static_assert(std::is_same<result_t, ref_t >::value, "");
Я ищу решение, которое работает на C ++ 11 и не использует никаких библиотек, кроме стандартной библиотеки C ++ 11. У меня также есть моя версия c ++ 14 с ручным управлением index_sequence
/ make_index_sequence
и может предоставлять списки нетипичных параметров в виде массивов, если это упрощает код.
Самое близкое, что я нашел до сих пор, это: Как создать декартово произведение списка типов?. Так что в принципе (я не проверял это) должна быть возможность превратить пакеты не типовых параметров в пакеты параметров типа, а затем применить решение в связанном посте, но я надеялся, что есть более простое / более короткое решение наряду линии этого:
template<int... Ints, char ... Chars>
auto magic(u_list<Ints...>, c_list<Chars...>)
{
//Doesn't work, as it tries to expand the parameter packs in lock step
return bar<foo<Ints,Chars>...>{};
}
using result_t = decltype(magic(int_vals{}, char_vals{}));
Вы можете сделать что-то вроде следующего:
template <int... Is>
using u_list = std::integer_sequence<int, Is...>;
template <char... Cs>
using c_list = std::integer_sequence<char, Cs...>;
template<int, char> struct foo {};
template<class ...> struct bar {};
template <std::size_t I, typename T, template <typename, T...> class C, T ... Is>
constexpr T get(C<T, Is...> c)
{
constexpr T values[] = {Is...};
return values[I];
}template <std::size_t I, typename T>
constexpr auto get_v = get<I>(T{});template<int... Ints, char ... Chars, std::size_t ... Is>
auto cartesian_product(u_list<Ints...>, c_list<Chars...>, std::index_sequence<Is...>)
-> bar<foo<
get_v<Is / sizeof...(Chars), u_list<Ints...> >,
get_v<Is % sizeof...(Chars), c_list<Chars...> >
>...
>;
template<int... Ints, char ... Chars>
auto cartesian_product(u_list<Ints...> u, c_list<Chars...> c)
-> decltype(cartesian_product(u, c, std::make_index_sequence<sizeof...(Ints) * sizeof...(Chars)>()));using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;
using result_t = decltype(cartesian_product(int_vals{}, char_vals{}));
Возможная реализация стандартной части:
template <typename T, T ... Is> struct integer_sequence{};
template <std::size_t ... Is>
using index_sequence = integer_sequence<std::size_t, Is...>;
template <std::size_t N, std::size_t... Is>
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...> {};
template <std::size_t... Is>
struct make_index_sequence<0u, Is...> : index_sequence<Is...> {};
И изменить в ответе:
template <std::size_t I, typename T, template <typename, T...> class C, T ... Is>
constexpr T get(C<T, Is...> c)
{
using array = T[];
return array{Is...}[I];
}
template<int... Ints, char ... Chars, std::size_t ... Is>
auto cartesian_product(u_list<Ints...>, c_list<Chars...>, index_sequence<Is...>)
-> bar<foo<
get<Is / sizeof...(Chars)>(u_list<Ints...>{}),
get<Is % sizeof...(Chars)>(c_list<Chars...>{})
>...
>;
На мой взгляд, гораздо проще выполнять шаблонное метапрограммирование в области чистых типов.
Требуется некоторая работа, чтобы переместиться из страны шаблонных параметров, не относящихся к типу, в страну типов и обратно, но это означает, что вы используете универсальные утилиты метапрограммирования вместо тех, которые специфичны для вашей проблемы.
Поэтому я сведу вашу проблему к декартовому произведению в списке типов.
Вот мой тип пакета:
template<class...Ts>struct types {
using type=types; // makes inheriting from it useful
static constexpr std::size_t size = sizeof...(Ts);
};
Сначала мы пишем fmap
, Fmap принимает функцию и список и возвращает список каждого элемента списка с примененной функцией.
template<template<class...>class Z, class List>
struct fmap {};
template<template<class...>class Z, class List>
using fmap_t = typename fmap<Z,List>::type;
template<template<class...>class Z, class...Ts>
struct fmap<Z, types<Ts...>>:
types<Z<Ts>...>
{};
а также fapply
, fapply также принимает функцию и список, но применяет функцию ко всему набору элементов списка.
template<template<class...>class Z, class List>
struct fapply {};
template<template<class...>class Z, class List>
using fapply_t=typename fapply<Z,List>::type;
template<template<class...>class Z, class...Ts>
struct fapply<Z, types<Ts...>> {
using type=Z<Ts...>;
};
Как это бывает, частичное применение fapply
очень полезно:
template<template<class...>class Z>
struct applier {
template<class List>
using apply = fapply_t<Z,List>;
};
Мы хотим иметь возможность объединять списки:
template<class...>
struct cat:types<> {};
template<class...As, class...Bs, class...Cs>
struct cat<types<As...>, types<Bs...>, Cs...>:
cat<types<As..., Bs...>, Cs...>
{};
template<class...As>
struct cat<types<As...>>:types<As...>{};
template<class...Ts>using cat_t=typename cat<Ts...>::type;
Тогда вот cart_product_t:
template<class A, class B>
struct cart_product {};
template<class A, class B>
using cart_product_t = typename cart_product<A,B>::type;
template<class A, class... Bs>
struct cart_product<types<A>, types<Bs...>>:
types< types<A, Bs>... >
{};
// reduce cart_product to cart_product on a one element list on the lhs:
template<class...As, class... Bs>
struct cart_product<types<As...>, types<Bs...>>:
fapply_t<
cat_t,
fmap_t<
applier<cart_product_t>::template apply,
types<
types< types<As>, types<Bs...> >...
>
>
>
{};
Типы, специфичные для вашей проблемы:
template<int...>struct u_list {};
template<char...>struct c_list {};
template<int, char>struct foo {};
template<class...>struct bar{};
Инструмент, который поднимает списки значений в типы:
template<class> struct lift {};
template<int...is> struct lift<u_list<is...>>:
types< std::integral_constant<int, is>... >
{};
template<char...is> struct lift<c_list<is...>>:
types< std::integral_constant<char, is>... >
{};
template<class T>using lift_t=typename lift<T>::type;
lower_to_foo принимает пару типов и преобразует их в foo:
template<class I, class C>
using lower_to_foo = foo<I::value, C::value>;
Теперь мы собрали их вместе:
using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;
using product = cart_product_t< lift_t<int_vals>, lift_t<char_vals> >;
static_assert( product::size == 6, "should be 6" );
using result_t = fapply_t< bar, fmap_t< applier<lower_to_foo>::template apply, product > >;
using ref_t = bar<
foo<1, -3>, foo<1, 3>,
foo<5, -3>, foo<5, 3>,
foo<7, -3>, foo<7, 3>
>;
ref_t test = result_t{}; // gives better error messages than static_assert
static_assert(std::is_same<result_t, ref_t >::value, "");
а боб твой дядя
cat
, fmap
а также fapply
обе относительно стандартные функции в функциональном программировании. applier
просто позволяет писать функции сопоставления шаблонов, используя элементы вместо списков (это частично применяется fapply
).
Теперь, помните, как я сказал, что метапрограммирование шаблонов было проще с типами?
Заметьте все эти параметры шаблона шаблона? Это становится легче, если они являются типами.
template<template<class...>class Z>
struct ztemplate {
template<class...Ts>using apply=Z<Ts...>;
};
и вы можете пройти весь путь до метапрограммирования constexpr в стиле hana с тегами типа и operator()
на ztemplate
и другие приколы.
Принимая кросс-продукт списка типов в качестве основы
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
template<int ...> struct u_list {};
template<char ...> struct c_list {};
template<int, char > struct foo {};
template<typename...> struct type_list {};
Мы расширяем char...
упаковать с row
template<int I, char... Cs>
struct row
{
typedef type_list<foo<I,Cs>...> type;
};
template <typename... T> struct concat;
template <typename... S, typename... T>
struct concat<type_list<S...>, type_list<T...>>
{
using type = type_list<S..., T...>;
};
Мы хотим дополнительную специализацию concat
вырваться из базового случая
template <typename... T>
struct concat<type_list<T...>, void>
{
using type = type_list<T...>;
};
template<typename I, typename C>
struct cross_product;
Базовый случай: нет больше целых
template<char... Cs>
struct cross_product<u_list<>, c_list<Cs...>>
{
using type = void;
};
Рекурсивный регистр: int, за которым следует набор целых
template<int I, int... Is, char... Cs>
struct cross_product<u_list<I, Is...>, c_list<Cs...>>
{
using type = typename concat<typename row<I,Cs...>::type, typename cross_product<u_list<Is...>, c_list<Cs...>>::type>::type;
};
int main()
{
using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;
using result_t = cross_product<int_vals, char_vals>::type;
using ref_t = type_list<
foo<1, -3>, foo<1, 3>,
foo<5, -3>, foo<5, 3>,
foo<7, -3>, foo<7, 3>
>;
static_assert(std::is_same<result_t, ref_t >::value, "");
return 0;
}
Ниже приведены мои 2 цента …
Если вы хотите общее решение, большая проблема, которую я вижу, это то, что от int_vals
а также char_vals
типы не легки (в C ++ 11; проще в C ++ 17) извлекать типы содержащихся значений (int
а также char
).
Итак, я полагаю, вы должны передать их magic<>
вместе с foo
а также bar
(если ты не хочешь foo
а также bar
жестко закодированы).
Так что призыв к magic<>
стать (по-моему)
using result_t
= typename magic<int, char, foo, bar, int_vals, char_vals>::type;
Ниже приведен полный рабочий пример с моим решением.
#include <type_traits>
template <int...> struct u_list {};
template <char...> struct c_list {};
template <int, char> struct foo {};
template <typename ...> struct bar {};
template <typename T1, typename T2, T1 t1, T2 ... T2s>
struct midProd
{ };
template <typename T1, typename T2, template <T1, T2> class, typename...>
struct magicHelper;
template <typename T1, typename T2,
template <T1, T2> class ResIn,
template <typename...> class ResOut,
typename ... R>
struct magicHelper<T1, T2, ResIn, ResOut<R...>>
{ using type = ResOut<R...>; };
template <typename T1, typename T2,
template <T1, T2> class ResIn,
template <typename...> class ResOut,
typename ... R, T1 ts1, T2 ... ts2, typename ... MpS>
struct magicHelper<T1, T2, ResIn, ResOut<R...>,
midProd<T1, T2, ts1, ts2...>, MpS...>
{ using type = typename magicHelper<T1, T2, ResIn,
ResOut<R..., ResIn<ts1, ts2>...>, MpS...>::type; };template <typename T1, typename T2,
template <T1, T2> class,
template <typename...> class,
typename, typename>
struct magic;
template <typename T1, typename T2,
template <T1, T2> class ResIn,
template <typename...> class ResOut,
template <T1...> class C1, template <T2...> class C2,
T1 ... ts1, T2 ... ts2>
struct magic<T1, T2, ResIn, ResOut, C1<ts1...>, C2<ts2...>>
{ using type = typename magicHelper<T1, T2, ResIn, ResOut<>,
midProd<T1, T2, ts1, ts2...>...>::type ; };
int main ()
{
using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;
using result_t
= typename magic<int, char, foo, bar, int_vals, char_vals>::type;
using ref_t = bar< foo<1, -3>, foo<1, 3>,
foo<5, -3>, foo<5, 3>,
foo<7, -3>, foo<7, 3> >;
static_assert(std::is_same<result_t, ref_t >::value, "");
}
Очевидно, что если вы предпочитаете жесткий код некоторых типов (u_list
, c_list
, foo
, а также bar
), решение стало намного проще
#include <type_traits>
template <int...> struct u_list {};
template <char...> struct c_list {};
template <int, char> struct foo {};
template <typename ...> struct bar {};
template <int, char...> struct midProd {};
template <typename...>
struct magicH;
template <typename ... R>
struct magicH<bar<R...>>
{ using type = bar<R...>; };
template <typename ... R, int i, char ... cs, typename ... MpS>
struct magicH<bar<R...>, midProd<i, cs...>, MpS...>
{ using type = typename magicH<bar<R..., foo<i, cs>...>, MpS...>::type; };template <typename, typename>
struct magic;
template <int ... is, char ... cs>
struct magic<u_list<is...>, c_list<cs...>>
{ using type = typename magicH<bar<>, midProd<is, cs...>...>::type; };
int main ()
{
using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;
using result_t = typename magic<int_vals, char_vals>::type;
using ref_t = bar< foo<1, -3>, foo<1, 3>,
foo<5, -3>, foo<5, 3>,
foo<7, -3>, foo<7, 3> >;
static_assert(std::is_same<result_t, ref_t >::value, "");
}
Так же, как другие в C ++ 17:
// Type your code here, or load an example.
#include <type_traits>
template<int ...>
struct u_list {};
template<char ...>
struct c_list {};
template<int, char >
struct foo {};
template<class ...>
struct bar {};
using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;
template<class... Args> struct type_list{
template<class> struct make_concat;
template<class ...Xs>
struct make_concat<type_list<Xs...>>{
using type = type_list<Args...,Xs...>;
};
template<class T>
using concat = typename make_concat<T>::type;
template<template<class...>class TT>
using applied_to = TT<Args...>;
};
template<
template<auto,auto> class C
,class X,class Y,class Yit=Y>
struct cart_prod;
template<template<auto,auto> class C,
template<auto...> class Xt,
template<auto...> class Yt,
class Yit,
auto Xi,auto...Xis,auto Yi,auto...Yis>
struct cart_prod<C,Xt<Xi,Xis...>,Yt<Yi,Yis...>,Yit>{
using type = typename type_list<class C<Xi,Yi>>
::template concat<typename cart_prod<C,Xt<Xi,Xis...>,Yt<Yis...>,Yit>::type>;
};
template<template<auto,auto> class C,
template<auto...> class Xt,
template<auto...> class Yt,
class Yit,
auto Xi,auto...Xis,auto Yi>
struct cart_prod<C,Xt<Xi,Xis...>,Yt<Yi>,Yit>{
using type = typename type_list<class C<Xi,Yi>>
::template concat<typename cart_prod<C,Xt<Xis...>,Yit,Yit>::type>;
};
template<template<auto,auto> class C,
template<auto...> class Xt,
template<auto...> class Yt,
class Yit,
auto Xi,auto Yi>
struct cart_prod<C,Xt<Xi>,Yt<Yi>,Yit>{
using type = type_list<class C<Xi,Yi>>;
};using result_t = cart_prod<foo,int_vals,char_vals>::type::applied_to<bar>;
using ref_t = bar<
foo<1, -3>, foo<1, 3>,
foo<5, -3>, foo<5, 3>,
foo<7, -3>, foo<7, 3>
>;
static_assert(std::is_same<result_t, ref_t >::value, "");
еще одно (но более короткое) решение может быть
template<typename Ret,typename R>
auto magic( bar<u_list<>, R>, Ret result, R ) { return result; }
template<int I, int... Ints, typename... Foos, typename R>
auto magic( bar<u_list<I,Ints...>, c_list<>>, bar<Foos...>, R rollback ) { return magic(
bar<u_list<Ints...>,R>{}, bar<Foos...>{}, rollback );}
template<int I, int... Ints, char J, char ... Chars, typename... Foos, typename R >
auto magic( bar<u_list<I,Ints...>, c_list<J,Chars...>>, bar<Foos...>, R rollback ) { return magic(
bar<u_list<I,Ints...>, c_list<Chars...>>{},
bar<Foos...,foo<I,J>>{},
rollback );}
using result_t = decltype(magic( bar<int_vals,char_vals>{}, bar<>{}, char_vals{} ));