【问题标题】:Building a compile time list incrementally in C++在 C++ 中以增量方式构建编译时间列表
【发布时间】:2014-07-28 02:38:30
【问题描述】:

在 C++ 中,有没有办法按照以下模式逐步构建编译时间列表?

START_LIST(List)
ADD_TO_LIST(List, int)
ADD_TO_LIST(List, float)
ADD_TO_LIST(List, double)
END_LIST(List)

这样的结果应该等价于:

using List = Cons<int, Cons<float, Cons<double, Nil>>>;

我还有一个限制,即宏之间的空间必须在整个范围内。我打算使用宏定义事物并同时将它们注册到列表中,如下所示:

#define DEFINE_ELEMENT(Name, Value) \
using Name = SomeTemplate<Value>; \
ADD_TO_LIST(ListOfElements, Name)

换句话说,不允许将START_LIST 定义为SomeTemplate&lt;decltype(。这样就不可能在两者之间添加新定义。

请注意,解决方案也可以采用“参数包”(可变参数模板)列表的形式。我只关心它遵循如上所示的增量定义模式。

可以在这里使用专业化吗?如果不能完全使用上述模式,是否可以使用更多样板?

【问题讨论】:

  • 我认为这将取决于编译器。您使用的是哪个编译器?我在上一份工作中确实看到过类似的事情。遗憾的是,我不记得细节了。
  • @abelenky 我希望有一个标准的 C++11 解决方案,但我想我会选择适用于 G++ 4.8 的任何东西。
  • 您可能会喜欢我的回答:stackoverflow.com/questions/18701798/…
  • 主要问题是后续行无法引用任何前面的行,因为它们都具有相同的名称和范围。是否可以为每一行指定前一行的名称,以便我们可以根据之前的结果进行构建?
  • 我不确定你想通过这个来完成什么。但请看:journal.stuffwithstuff.com/2012/01/24/higher-order-macros-in-c 这使您可以在实例时分配宏,可能会解决您的问题。

标签: c++ list templates template-meta-programming cons


【解决方案1】:

您可以直接使用 C++11 可变参数模板,它们允许以比经典函数式 head:tail 方法更花哨的方式编写类型列表:

template<typename... Ts>
struct list{};

using l = list<int,char,bool>;

另一方面,如果您喜欢头尾方式,则可以从一种格式转换为另一种格式。在这种情况下(从变量到函数):

template<typename HEAD , typename TAIL>
struct list{};

struct nil{};

template<typename... Ts>
struct make_list;

template<typename HEAD , typename... TAIL>
struct make_list<HEAD,TAIL>
{
    using result = list<HEAD,typename make_list<TAIL...>::result;
};

template<>
struct make_list<>
{
    using result = nil;
};

一个例子:

//l is list<int,list<char,list<bool,nil>>>
using l = typename make_list<int,char,bool>::result;

当然你可以使用模板别名来使语法更清晰:

template<typename... Ts>
using make = typename make_list<Ts...>::result;

using l = make<int,char,bool>;

【讨论】:

  • 我知道可变参数模板,但我更喜欢 cons 列表,因为它们更易于使用且速度更快。无论如何,您的回答并不能解决我的问题。正如您自己所指出的,您可以在两种列表格式之间进行转换,因此我是否根据参数包列表的 cons 列表来表达问题并不重要。
  • 请注意,我的问题的解决方案将涉及对 START_TO_LIST、ADD_TO_LIST 和 END_LIST 宏的适当定义。
  • @AmbrozBizjak:缺点列表更难处理,而且速度更慢。你只是不习惯可变参数。
  • @MooingDuck:我前段时间用 G++ 进行的测试表明,可变参数模板的简单递归分解具有二次性能。我的理由是,每次使用参数包实例化模板时,都会复制整个包。使用 cons list,尾巴就被简单地拿走了。
  • 考虑实现一个transform 元函数(map 函数式程序员):使用可变参数模板,它只是函数应用程序的一个包扩展,使用 head:tail 你必须写坑手动递归遍历。 (我非常了解这一点,我目前正在为基于可变参数的类型列表和迭代器范围编写算法:P github.com/Manu343726/Turbo/blob/reboot/algorithm.hpp
【解决方案2】:

在 OP 自己的解决方案中,它只适用于全局范围,不适用于类范围,也不适用于函数范围。我在这里的实现适用于所有全局、类和函数范围。与 OP 的解决方案相比,另一个优势是我的解决方案允许多个列表 START_LIST/END_LIST 对重叠,即不同的列表结构可以交错。

一个小限制是它使用__COUNTER__ 宏,它不是starndard 的一部分,但它得到了gcc、clang 和MSVC 的良好支持,因此可移植性在这里不是一个大问题。另一件事是对于函数范围,它必须使用单独的宏 START_LIST_FUNCADD_TO_LIST_FUNC 因为我使用函数重载解析但在函数范围内它不能声明 static 函数,而在类级别它必须使用static函数。

编辑:从 OP 的评论中加入 ListReverseHelper 的想法,使其更简单。

#include <iostream>
#include <typeinfo>
using namespace std;

struct Nil {};

template <typename T, typename U> struct Cons {};

template <typename List, typename Reversed> struct ListReverseHelper;

template <typename Reversed>
struct ListReverseHelper<Nil, Reversed> {
  using Type = Reversed;
};

template <typename Head, typename Tail, typename Reversed>
struct ListReverseHelper<Cons<Head, Tail>, Reversed> {
  using Type = typename ListReverseHelper<Tail, Cons<Head, Reversed>>::Type;
};

template <typename T, int N> struct ListMakerKey : ListMakerKey<T, N-1> {};
template <typename T> struct ListMakerKey<T, 0> {};

#define START_LIST_(name, modifier) \
  struct name##_ListMaker {}; \
  modifier Nil list_maker_helper_(ListMakerKey<name##_ListMaker, __COUNTER__>);
#define ADD_TO_LIST_(name, type, modifier) \
  modifier Cons<type, decltype(list_maker_helper_(ListMakerKey<name##_ListMaker, __COUNTER__>{}))> \
  list_maker_helper_(ListMakerKey<name##_ListMaker, __COUNTER__>);
#define END_LIST(name) \
  using name = typename ListReverseHelper<decltype(list_maker_helper_(ListMakerKey<name##_ListMaker, __COUNTER__>{})), Nil>::Type;

#define START_LIST(name) START_LIST_(name, static)
#define ADD_TO_LIST(name, type) ADD_TO_LIST_(name, type, static)
#define START_LIST_FUNC(name) START_LIST_(name,)
#define ADD_TO_LIST_FUNC(name, type) ADD_TO_LIST_(name, type,)

START_LIST(List)
ADD_TO_LIST(List, int)
int a = 10;
ADD_TO_LIST(List, float)
int b = 10;
START_LIST(List2)
ADD_TO_LIST(List, int)
int c = 10;
ADD_TO_LIST(List2, float)
ADD_TO_LIST(List, double)
ADD_TO_LIST(List2, int)
ADD_TO_LIST(List2, float)
END_LIST(List2)
ADD_TO_LIST(List, double)
ADD_TO_LIST(List, char)
END_LIST(List)

struct A {
  START_LIST(List3)
  ADD_TO_LIST(List3, int)
  int a = 10;
  ADD_TO_LIST(List3, float)
  int b = 10;
  ADD_TO_LIST(List3, double)
  ADD_TO_LIST(List3, int)
  END_LIST(List3)
};

int main() {
  START_LIST_FUNC(List4)
  ADD_TO_LIST_FUNC(List4, char)
  int a = 10;
  ADD_TO_LIST_FUNC(List4, float)
  int b = 10;
  ADD_TO_LIST_FUNC(List4, int)
  ADD_TO_LIST_FUNC(List4, char)
  END_LIST(List4)
  List x;
  List2 y;
  A::List3 z;
  List4 w;
  cout << typeid(x).name() << endl;
  cout << typeid(y).name() << endl;
  cout << typeid(z).name() << endl;
  cout << typeid(w).name() << endl;
}

g++-4.8下编译:

[hidden]$ g++ -std=c++11 x.cpp && c++filt -t `./a.out`
Cons<int, Cons<float, Cons<int, Cons<double, Cons<double, Cons<char, Nil> > > > > >
Cons<float, Cons<int, Cons<float, Nil> > >
Cons<int, Cons<float, Cons<double, Cons<int, Nil> > > >
Cons<char, Cons<float, Cons<int, Cons<char, Nil> > > >

【讨论】:

  • 这个问题是我不能在 ADD_TO_LIST 调用之间插入任意定义。
  • @AmbrozBizjak:您想在两者之间添加任意定义吗?这很重要,应该在问题中澄清,最好在样本中澄清。 (此外,这将其推向了不可能的领域)
  • @AmbrozBizjak,在您的回答中, ADD_TO_LIST 包含模板特化,不能在块范围内声明。即您的宏不能在函数中使用。如果我们只是想在全局范围内声明类型,那么有些方法在您的解决方案中没有限制。
  • @icando 是的,我不需要它在函数中工作,只在全局和类范围内工作(类范围需要一个虚拟模板参数)。那么,还有什么办法呢?
【解决方案3】:

我找到了解决方案!虽然它有点有限;您需要为每个元素提供一个唯一的名称,并且元素数量需要有一个上限(此处为 100)。

#include <type_traits>

// Good old Cons and Nil.
template <typename THead, typename TTail>
struct Cons {
    using Head = THead;
    using Tail = TTail;
};
struct Nil {};

// Helper template which builds a list from a template
// providing elements at given indices.
template <template<int> class ElemAt, int Offset, int Length>
struct BuildListFromElemAt {
    using Result = Cons<typename ElemAt<Offset>::Elem, typename BuildListFromElemAt<ElemAt, (Offset + 1), (Length - 1)>::Result>;
};
template <template<int> class ElemAt, int Offset>
struct BuildListFromElemAt<ElemAt, Offset, 0> {
    using Result = Nil;
};

// This is where the abuse begins.
// We use these classes to keep the current length
// of the list, in combination with function overloads.
// Each time we add an element, we will provide a more
// specific overload of a helper function.
template <int N>
struct Counter : public Counter<(N - 1)> {
    static int const Num = N;
};
template <>
struct Counter<0> {
    static int const Num = 0;
};

// At the beginning, we define the initial size function
// taking Counter<0>, and declare the ElemAt template.
#define BEGIN_LIST(ListName) \
Counter<0> ListName##_Size (Counter<0>); \
template <int Index> struct ListName##_ElemAt;

// For each element, we retrieve the current length of the
// list by checking the return type of calling the size function
// with a very large Counter.
// Then we overload the size function for one greater Counter,
// which ensures that we get an incremented length at the next
// step. We also define the ElemAt for this index to return the
// given Value.
#define ADD_TO_LIST(ListName, Name, Value) \
static int const ListName##_##Name##_PrevLen = decltype(ListName##_Size(Counter<100>()))::Num; \
static int const ListName##_##Name##_Len = ListName##_##Name##_PrevLen + 1; \
Counter<ListName##_##Name##_Len> ListName##_Size (Counter<ListName##_##Name##_Len>); \
template <> struct ListName##_ElemAt<ListName##_##Name##_PrevLen> { \
    using Elem = Value; \
};

// At the end, we retrieve the final length of the list,
// and build the list using the BuildListFromElemAt template.
#define END_LIST(ListName) \
static int const ListName##_Len = decltype(ListName##_Size(Counter<100>()))::Num; \
using ListName = typename BuildListFromElemAt<ListName##_ElemAt, 0, ListName##_Len>::Result;

// Here we go...
BEGIN_LIST(List)
ADD_TO_LIST(List, First, int)
ADD_TO_LIST(List, Second, float)
ADD_TO_LIST(List, Third, double)
END_LIST(List)

// And it works :)
static_assert(std::is_same<typename List::Head, int>::value, "");
static_assert(std::is_same<typename List::Tail::Head, float>::value, "");
static_assert(std::is_same<typename List::Tail::Tail::Head, double>::value, "");

【讨论】:

  • ADD_TO_LIST 包含模板特化,不能在块范围内声明。即您的宏不能在函数中使用。如果我们只是想在全局范围内声明类型,那么有些方法在您的解决方案中没有限制。
  • 我现在更新了我的答案,它适用于全局、类和函数范围。 C++ 是一种魔法!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-28
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多