Я создаю общую коллекцию Node
s. Каждый Node
имеет тип Start
и End
. И тип End
одного должен совпадать с типом Start
следующего.
Если бы я перечислил все типы в коллекции, конструктор выглядел бы так (для четырех типов):
template <typename Start, typename End>
class Node {
};
template <typename A, typename B, typename C, typename D>
class Collection
{
public:
Collection(Node<A, B> n1, Node<B, C> n2, Node<C, D> n3) { }
};
Но когда я пытаюсь написать конструктор как вариативный шаблон для поддержки любого количества типов, я теряюсь.
Можете ли вы использовать С++ 17?
С некоторой косвенностью вы можете сделать:
template <typename Start, typename End>
class Node {
// ...
};
// Implementation using the Nodes
// You might add typedef in Node to retrieve Start/End if needed (or create traits)
template <typename ... Nodes>
struct CollectionImpl
{
CollectionImpl(Nodes ... ns) : nodes(ns...){}
std::tuple<Nodes...> nodes; // You probably want something like that
};
// Helper class to build the type
template <typename Seq, typename Tup> struct CollectionMaker;
template <std::size_t ... Is, typename Tuple>
struct CollectionMaker<std::index_sequence<Is...>, Tuple>
{
using type = CollectionImpl<Node<std::tuple_element_t<Is, Tuple>,
std::tuple_element_t<Is + 1, Tuple>>...>;
};
// Wanted interface.
template <typename ... Ts>
using Collection = typename CollectionMaker<std::make_index_sequence<sizeof...(Ts) - 1>,
std::tuple<Ts...>>::type;
Я предлагаю немного другое решение.
Учитывая тривиальную структуру tag
для переноса универсального типа (чтобы избежать проблем с типами, которые по умолчанию не конструируются в std::tuples
s)
template <typename>
struct tag
{ };
и вспомогательная структура, которая определяет 2 типа на основе std::tuple
template <typename...>
struct getTpls;
template <std::size_t ... Is, typename ... Ts>
struct getTpls<std::index_sequence<Is...>, Ts...>
{
using tpl0 = std::tuple<tag<Ts>...>;
using ftpl = std::tuple<std::tuple_element_t<Is, tpl0>...>;
using stpl = std::tuple<std::tuple_element_t<1u+Is, tpl0>...>;
};
вы можете написать Collection
следующим образом
template <typename ... Ts>
struct Collection
{
static_assert( sizeof...(Ts) > 1u, "more types, please");
using getT = getTpls<std::make_index_sequence<sizeof...(Ts)-1u>, Ts...>;
using ftpl = typename getT::ftpl;
using stpl = typename getT::stpl;
template <typename ... FTs, typename ... STs,
std::enable_if_t<
std::is_same_v<ftpl, std::tuple<tag<FTs>...>>
&& std::is_same_v<stpl, std::tuple<tag<STs>...>>, int> = 0>
Collection (Node<FTs, STs> ...)
{ }
};
Ниже приведен полный пример компиляции
#include <tuple>
#include <type_traits>
template <typename Start, typename End>
class Node
{ };
struct A {};
struct B {};
struct C {};
template <typename>
struct tag
{ };
template <typename...>
struct getTpls;
template <std::size_t ... Is, typename ... Ts>
struct getTpls<std::index_sequence<Is...>, Ts...>
{
using tpl0 = std::tuple<tag<Ts>...>;
using ftpl = std::tuple<std::tuple_element_t<Is, tpl0>...>;
using stpl = std::tuple<std::tuple_element_t<1u+Is, tpl0>...>;
};
template <typename ... Ts>
struct Collection
{
static_assert( sizeof...(Ts) > 1u, "more types, please");
using getT = getTpls<std::make_index_sequence<sizeof...(Ts)-1u>, Ts...>;
using ftpl = typename getT::ftpl;
using stpl = typename getT::stpl;
template <typename ... FTs, typename ... STs,
std::enable_if_t<
std::is_same_v<ftpl, std::tuple<tag<FTs>...>>
&& std::is_same_v<stpl, std::tuple<tag<STs>...>>, int> = 0>
Collection (Node<FTs, STs> ...)
{ }
};
int main ()
{
Collection<A, B, C> c0{Node<A, B>{}, Node<B, C>{}}; // compile
// Collection<A, B, B> c1{Node<A, B>{}, Node<B, C>{}}; // error!
}
Спасибо, мне нравится, как ваш ответ оставляет конструктор больше всего похожим на предполагаемые данные, которые его заполняют. Я не думаю, что шаблоны должны когда-либо запутывать цель функции.
Часть, которой мне не хватало в моем коде, заключалась в том, как вывести типы FT и ST. Я не знал, что для этого можно использовать enable_if.
На основе ответа max66:
Это убирает ненужную структуру tag
и упрощает index_sequence
прямую рекурсию (аналогично определению кортежа).
template <typename Owner, typename Value>
class Node {
};
struct A {};
struct B {};
struct C {};
struct D {};
template <typename First, typename...Rest>
std::tuple<First, Rest...> tuple_push_front(std::tuple<Rest...>);
template <typename T1, typename T2, typename...T>
struct NodeCollector {
private:
using nodeRest = NodeCollector<T2, T...>;
public:
using tplOwners = decltype(tuple_push_front<T1>(std::declval<typename nodeRest::tplOwners>()));
using tplValues = decltype(tuple_push_front<T2>(std::declval<typename nodeRest::tplValues>()));
};
template <typename T1, typename T2>
struct NodeCollector<T1, T2> {
public:
using tplOwners = std::tuple<T1>;
using tplValues = std::tuple<T2>;
};
template <typename...Ts>
class Collection
{
static_assert( sizeof...(Ts) > 1u, "Collection requires at least two types.");
private:
using nodeCollector = NodeCollector<Ts...>;
public:
template <typename...OTs, typename...VTs, typename=std::enable_if_t<
(std::is_same_v<typename nodeCollector::tplOwners, std::tuple<OTs...>> &&
std::is_same_v<typename nodeCollector::tplValues, std::tuple<VTs...>>)> >
Collection(Node<OTs, VTs>...) { }
};
int main()
{
Collection<A, B, C, D> c{Node<A, B>{}, Node<B, C>{}, Node<C, D>{}};
std::cout << demangle(typeid(c).name()) << std::endl;
}
Было бы здорово показать вашу попытку написать конструктор с вариативным шаблоном. Возможно, мы сможем не только помочь, но и проанализировать ваш мыслительный процесс и указать, где именно логика, которую вы придумали, ошибочна.