Ваша идея сложна для понимания, но я думаю, что вы хотите взять список template<typename> typename
s и равное число typename
s и сконструировать продукт из их заархивированного приложения?
// wrap a template<typename> typename into a normal type
// e.g. index_of_v<std::vector, std::vector> fails, but
// index_of_v<suspend<std::vector>, suspend<std::vector>> gives 0
template<template<typename> typename>
struct suspend { };
// search for X in Xs and produce a static constexpr std::size_t member values containing the index
// This is just the natural functional programming way to search a linked list
// indexOf(x, Nil) = undefined
// indexOf(x, Cons(y, xs)) = x == y ? 0 : 1 + indexOf(x, xs)
// deriving from std::integral_constant is really just noise;
// could be replaced with an explicit member definition
// but it's considered idiomatic to do this for some reason that I've forgotten
template<typename X, typename... Xs>
struct index_of { }; // base case, the default template only fires when Xs is empty, so the index is undefined
template<typename X, typename... Xs>
struct index_of<X, X, Xs...> : std::integral_constant<std::size_t, 0> { }; // if X is at the top of the list, it has index 0
template<typename X, typename Y, typename... Xs>
struct index_of<X, Y, Xs...> : std::integral_constant<std::size_t, index_of<X, Xs...>::value + 1> { }; // if it isn't, find it's index relative to the tail of the list, then shift it to be the index relative to the whole
// instead of writing index_of<X, Xs..>::value, write index_of_v<X, Xs...>
// this is a convention that you see in the standard library
template<typename X, typename... Xs>
inline constexpr std::size_t index_of_v = index_of<X, Xs...>::value;
// a class cannot have two lists of variadic template parameters
// the easiest thing to do is split the templating into two stages
// the outer template, choices, takes a list of templates as parameters
// template<typename T> class std::vector;
// int is a "base" type, so you can pass int to std::vector<int>
// but std::vector is not like int: std::vector<std::vector> is meaningless
// std::vector alone is just a template<typename> typename
// a function from types to types
template<template<typename> typename... Cs>
struct choices {
// 2nd "stage" takes the list of "normal" types
template<typename... Ts>
class type {
// apply each template to the corresponding base type
// the resulting list of base types is passed to std::tuple
// there's a "field" for each Cs<Ts> inside the tuple
std::tuple<Cs<Ts>...> parts;
public:
// hopefully self-explanatory
type(Cs<Ts>... parts) : parts(parts...) { }
// return the result of calling foo() on the container identified by C
template<template<typename> typename C>
auto foo() {
// we know the list of all the Cs,
// so just find the index of C in it and pass that to std::get
// then pass in parts to get the desired object, then call foo()
return std::get<index_of_v<suspend<C>, suspend<Cs>...>>(parts).foo();
}
};
// there's no luck for deducing Cs, since in order to *get* to types we need to specify Cs
// (choices::type is not a thing, you always write choices<Cs...>::type)
// But you can deduce `Ts`, by looking at the things contained in each container
// so, deduction guide
template<typename... Ts>
type(Cs<Ts>...) -> type<Ts...>;
};
Ничего особенно интересного здесь не происходит. У вас есть список шаблонов контейнеров и список содержащихся типов, и каждый объект содержит кортеж объектов нужных типов. Что-то должно быть передано foo
, чтобы он знал, какой объект получить; Самый разумный вариант - это тип контейнера. Вы также можете использовать содержащийся тип, но, очевидно, они не уникальны, так что это не сработает. Вы также можете просто передать индекс напрямую. Поскольку мы не знаем весь тип объекта, есть вспомогательная функция типа index_of
, чтобы найти необходимый индекс для передачи в std::get
. Для простоты index_of
принимает только typename
аргументов. Так как мы хотим найти template<typename> typename
в списке таких, все они обернуты в suspend
, чтобы заставить его работать.
Вы восстанавливаете два типа A
, например:
template<typename T>
struct B {
T x;
B(T x) : x(x) { }
T foo() { return x; }
};
using A1 = choices<B>;
void demonstration1() {
A1::type a(B(5)); // note the deduction guide at work
std::cout << "B: " << a.foo<B>() << "\n";
}
template<typename T>
struct C {
T x;
C(T x) : x(x) { }
T foo() { return -x; } // keeping it interesting
};
using A2 = choices<B, C>;
void demonstration2() {
A2::type a(B('a'), C(5));
std::cout << "B: " << a.foo<B>() << "; C: " << a.foo<C>() << "\n";
}
Годболт