【问题标题】:Concatenating lists using template metaprogramming in C++11在 C++11 中使用模板元编程连接列表
【发布时间】:2021-04-27 13:23:39
【问题描述】:

我创建了一个名为 IntList 的新类型,它表示整数列表。这是使用模板制作的:

template<int...>
struct IntList;

template<int h, int... t>
struct IntList<h, t...>{
    constexpr static int head = h;
    typedef IntList<t...> next;
    constexpr static int size = sizeof...(t) + 1;
    constexpr static bool empty = false;
};

template<>
struct IntList<>{
    constexpr static int size = 0;
    constexpr static bool empty = true;
};

例如,IntList 是 4 个元素的列表 - 1,2,3,4。

IntList<1,2,3,4>::head; //Should be 1
IntList<1,2,3,4>::size; //Should be 4
IntList<1,2,3,4>::next; //Should be IntList<2,3,4>

现在,我想使用模板来创建一个连接这些类型列表的新类型。它将被称为 ConcatedIntLists。 如果我只需要连接两个列表,那就很简单了:

template<typename...>
struct ConcatedIntLists;

template<int...T1, int...T2>
struct ConcatedIntLists<IntList<T1...>, IntList<T2...>>{
    typedef IntList<T1..., T2...> list;
};

但是如果我想连接未知数量的列表怎么办?例如:

ConcatedIntLists<IntList<1,2,3>, IntList<>, IntList<4,5>>::list; //Should be IntList<1,2,3,4,5>
ConcatedIntLists<IntList<1>, IntList<2>, IntList<3>, IntList<4>>::list; //Should be IntList<1,2,3,4>

这是我陷入的部分。

【问题讨论】:

  • 为什么两个 IntList 的串联不能简单地成为另一个 IntList?
  • @NathanPierson 你可以通过在末尾写“::list”来提取它。

标签: c++ c++11 templates template-meta-programming


【解决方案1】:

您可以添加这个额外的专业化:

template<int...T1, typename...Ts>
struct ConcatedIntLists<IntList<T1...>, Ts...> {
    typedef typename ConcatedIntLists<IntList<T1...>,
                                      typename ConcatedIntLists<Ts...>::list>::list list;
};

Demo.

【讨论】:

  • 请在您的解决方案中添加对代码的解释。此外,它看起来一点也不正确,我不清楚这是如何解决问题的。
  • @cigien:这是要添加的额外专业。确实缺少一些额外的文字。
  • @cigien 代码是不言自明的。并且它至少在 VS2019 C++17 ConcatIntList 中工作超过 2 个 IntLists 将递归调用自身,在其余参数上传递第一个列表和 ConcatIntList 的结果......当参数模板数为 2 时,来自 OP 的 ConcatIntList 是用过。
  • 是的,你是对的,它确实有效,而且它更清洁。不过,补充说明总是很好。
  • 这就是我在意识到我可以简单地使用“typename...”而不是直接写“IntList...”之后最终所做的。完美运行。谢谢!
【解决方案2】:

这个解决方案是对我原来的解决方案的改进,由 Jarod42 在 cmets 中提出。

你可以递归地写这个模板:

template<typename ...T>
struct ConcatedIntLists;  // just to allow specializations
                          // for base case, and general case 

template<typename T>
struct ConcatedIntLists<T> 
{
    using list = T;             // base case: just a list
};

// for 2 or more lists
template<int...T1, int...T2, typename ...CRest>
struct ConcatedIntLists<IntList<T1...>, IntList<T2...>, CRest...>    
{
    using list = typename ConcatedIntLists<IntList<T1..., T2...> , CRest...>::list;
                                       //  ^concatenate first two, ^and the rest      
}

这是demo

请注意,此解决方案使用using 语法而不是typedef 语法,因为它更易于阅读。

【讨论】:

  • 漂亮干净的解决方案。
  • @Jarod42 我不这么认为。你的意思是我可以为基本情况做template&lt;int...T1, int...T2&gt;?这行不通。
  • @Jarod42 哦,这样好多了。我认为您不是要写一个新答案吗?
  • @Jarod42 好的,我会这样做的。我不知道为什么我在玩conditional_t 时挂断了它。谢谢。
  • 非常好的和干净的解决方案。谢谢!
【解决方案3】:

这些事情可以通过在类型列表上使用左折叠来解决。 由于我们无法迭代可变参数模板参数包,我们需要求助于递归。 如果您不习惯模板元编程,代码可能会有点难以理解,因此我在代码示例中添加了一些解释性 cmets。

// This is the functor we want to fold the elements over.
// It simply concatenates two IntLists.
template<typename L1, typename L2>
struct ConcateIntListsFunctor;

// The actual functor is a so called meta function, it's arguments
// are the template parameters in the primary template. 
template<int...T1, int...T2>...
struct ConcateIntListsFunctor<IntList<T1...>, IntList<T2...>>{
    // ... and we "call" it by retrieving it's "type" typedef.
    typedef IntList<T1..., T2...> type;
};


template<template<typename, typename> class, typename...>
struct LeftFold;

// The LeftFold is a meta-function that takes a binary meta-function as first parameter, a State and a list of types we want to fold over.
template<template<typename, typename> class Func,  typename State, typename T, typename... Ts>
struct LeftFold<Func, State, T, Ts...> {
    typedef typename Func<State, T>::type NewState; 
    // Here is where the recursion happens.
    typedef typename LeftFold<Func, NewState, Ts...>::type type;  
};

template<template<typename, typename> class Func, typename State, typename T>
struct LeftFold<Func, State, T> {
    typedef typename Func<State, T>::type type;
};

template<typename L0, typename... Ls>
struct ConcatenateIntLists {
    typedef typename LeftFold<ConcateIntListsFunctor, L0, Ls...>::type type;
};

现场示例here

【讨论】:

  • 这很好。我注意到您的演示使用编译器错误消息来查看结果。我个人认为最好使用static_assert 来检查类型是否匹配。在源代码中做尽可能多的事情总是好的。如果您愿意,请随意调整我的演示中的示例。
  • 是的,我同意。这只是一种习惯,因为我正在做很多元编程,这在开发过程中非常有用,因为您不必一遍又一遍地手动输入预期的类型。大多数情况下,我只是想确切地知道我得到什么类型,而这并不总是可以使用普通的static_assert
  • 那个类型打印工具更像是一个调试工具,而静态断言将是一个测试用例。我承认,这里肯定更好。但我将把它留在示例中,因为它在某些情况下可能是一个有用的技巧。
猜你喜欢
  • 2012-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-23
相关资源
最近更新 更多