Я пытаюсь вычислить n-арное декартово произведение вариативных шаблонов и пока не нашел элегантного метода для n-арного случая (унарный и бинарный легко, см. ниже).
Вот текущее состояние расследования. Для удобства я ввожу pack<Types...>
для хранения вариативных шаблонов. А затем некоторые полезные функции и помощники:
// Preamble
#include <iostream>
#include <type_traits>
// Helper function to display the result
template <class...> void print() {std::cout << __PRETTY_FUNCTION__ << "\n";}
// Pack to hold variadic templates
template <class...> struct pack {};
// Sum of packs
template <class...>
struct pack_sum;
// Sum of packs: single pack specialization
template <class... Types>
struct pack_sum<pack<Types...>> {
using type = pack<Types...>;
};
// Sum of packs: n-ary recursive specialization
template <class... Types1, class... Types2, class... Packs>
struct pack_sum<pack<Types1...>, pack<Types2...>, Packs...> {
using type = typename pack_sum<pack<Types1..., Types2...>, Packs...>::type;
};
Основываясь на этом, я могу представить свой декартовый продукт (унарную и бинарную версии):
// Cartesian product of packs
template <class...>
struct pack_product;
// Cartesian product of packs: single empty pack specialization
template <>
struct pack_product<pack<>> {
using type = pack<>;
};
// Cartesian product of packs: empty pack specialization
template <class... Types2, class... Packs>
struct pack_product<pack<>, pack<Types2...>, Packs...> {
using type = typename pack_product<pack<>, Packs...>::type;
};
// Cartesian product of packs: unary specialization
template <class... Types>
struct pack_product<pack<Types...>> {
using type = pack<pack<Types>...>;
};
// Cartesian product of packs: binary specialization
template <class Type1, class... Types1, class... Types2>
struct pack_product<pack<Type1, Types1...>, pack<Types2...>> {
using type = typename pack_sum<
pack<pack<Type1, Types2>...>,
typename pack_product<pack<Types1...>, pack<Types2...>>::type
>::type;
};
Что я могу проверить с помощью:
// Test
int main(int argc, char* argv[]) {
// Packs
using empty_pack = pack<>;
using char_pack = pack<signed char, char, unsigned char>;
using int_pack = pack<int, unsigned int>;
using float_pack = pack<float, double, long double>;
// Tests
print<typename pack_product<empty_pack>::type>();
print<typename pack_product<char_pack>::type>();
print<typename pack_product<empty_pack, char_pack>::type>();
print<typename pack_product<char_pack, empty_pack>::type>();
print<typename pack_product<char_pack, float_pack>::type>();
}
Весь функциональный код здесь: https://godbolt.org/z/Pv15W95EK
Результат:
typename pack_product<
pack<signed char, char, unsigned char>,
pack<float, double, long double>
>::type
является:
pack<
pack<signed char, float>,
pack<signed char, double>,
pack<signed char, long double>,
pack<char, float>,
pack<char, double>,
pack<char, long double>,
pack<unsigned char, float>,
pack<unsigned char, double>,
pack<unsigned char, long double>
>
как и ожидалось.
Теперь ПРОБЛЕМА:
Я ищу n-арную версию, но она позволяет избежать вложенных пакетов, так что:
pack_product<pack<A1, A2>, pack<B1, B2>, pack<C1, C2>>
дает следующие результаты:
pack<
pack<A1, B1, C1>, pack<A1, B1, C2>, pack<A1, B2, C1>, pack<A1, B2, C2>,
pack<A2, B1, C1>, pack<A2, B1, C2>, pack<A2, B2, C1>, pack<A2, B2, C2>
>
и НЕТ какой-то:
pack<
pack<A1, pack<B1, C1>>, pack<A1, pack<B1, C2>>, pack<A1, pack<B2, C1>>, pack<A1, pack<B2, C2>>,
pack<A2, pack<B1, C1>>, pack<A2, pack<B1, C2>>, pack<A2, pack<B2, C1>>, pack<A2, pack<B2, C2>>,
>
ВОПРОС: Во время поиска я нашел чрезвычайно уродливые версии, которые, казалось, работали, но я хотел бы что-то настолько чистое и элегантное, насколько это возможно, с минимальным количеством вспомогательных структур и специализаций. Если n-арная специализация покрывает бинарную версию, это плюс, но я не уверен, что это возможно. Любая помощь будет очень приветствоваться :)
ПРИМЕЧАНИЕ: Возможная специализация для того, что я ищу, была бы, но это всего лишь предложение:
template <class Type1, class... Types1, class... Types2, class... Pack>
struct pack_product<pack<Type1, Types1...>, pack<Types2...>, Pack...> {
using type = /* THE THING I'M LOOKING FOR */
};
NOTE: A possible specialization for the thing I'm looking for would be, but that is just a suggestion:
Для n-арной версии вам нужно только рекурсивно выполнить двоичную версию, поскольку pack_product<a, b, c>
эквивалентно pack_product<pack_product<a, b>, c>
// Cartesian product of packs: n-ary specialization
template <class Type1, class... Types1, class... Types2, class... Pack>
struct pack_product<pack<Type1, Types1...>, pack<Types2...>, Pack...> {
using type = typename pack_product<typename pack_sum<
pack<pack<Type1, Types2>...>,
typename pack_product<pack<Types1...>, pack<Types2...>>::type
>::type, Pack...>::type;
};
Однако это порождает
{pack<pack<pack<signed char, float>, int>, pack<pack<signed char, float>, unsigned int>, ...}
Чтобы удалить вложенные пакеты, можно выполнить частичную специализацию по специализации binary/n-arg.
// Cartesian product of packs: binary specialization for nested packs
template <class... Types, class... Types1, class... Types2>
struct pack_product<pack<pack<Types...>, Types1...>, pack<Types2...>> {
using type = typename pack_sum<
pack<pack<Types..., Types2>...>,
typename pack_product<pack<Types1...>, pack<Types2...>>::type
>::type;
};