【问题标题】:Expand Variadic Template Pack by Pairs成对展开可变参数模板包
【发布时间】:2019-05-29 19:15:09
【问题描述】:

我正在创建Nodes 的通用集合。每个Node 都有一个StartEnd 类型。并且其中一个的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) { }
};

但是当我尝试将构造函数编写为可变参数模板以支持任意数量的类型时,我被难住了。

【问题讨论】:

  • 很高兴展示您尝试使用可变参数模板编写构造函数。我们不仅可以提供帮助,还可以分析您的思维过程并指出您提出的逻辑到底存在缺陷的地方。
  • 你会用 C++17 吗?

标签: c++ c++17 variadic-templates sfinae template-argument-deduction


【解决方案1】:

我提出了一些不同的解决方案。

给定一个简单的 tag 结构来包装泛型类型(以避免在 std::tupless 中无法默认构造的类型出现问题)

template <typename>
struct tag
 { };

和一个基于std::tuple定义2种类型的辅助结构

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 来做到这一点。
【解决方案2】:

有些间接,你可以这样做:

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;

Demo

【讨论】:

    【解决方案3】:

    基于 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;
    }
    

    【讨论】:

      猜你喜欢
      • 2012-04-09
      • 2014-10-30
      • 2020-03-19
      • 2019-03-23
      • 2015-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多