BOOST_PP_SEQ_FOLD_LEFT如何工作?

时间:2018-01-17 18:07:25

标签: c++ c c-preprocessor

我需要编写一个宏来处理任意长的列表,例如(A)(B)(C)。如果我可以采用Boost依赖,我只会使用BOOST_PP_SEQ_系列宏之一。不幸的是,我不能这样做,所以我只想弄清楚它是如何工作的。这个东西并不明显。

这里的任何人都可以写一个简单的,独立的实现,比如BOOST_PP_SEQ_FOLD_LEFT供我查看吗?特别是,我想改造:

template_(class A, class B, class C)(
    requires IsFoo<A> && IsBar<B>)(
    requires IsBaz<C>)
void frobozzle(A, B, C);

被重写为:

template<class A, class B, class C,
    int dummy = 0,
    std::enable_if_t<dummy == 0 && (IsFoo<A> && IsBar<B>), int> = 0,
    std::enable_if_t<dummy == 0 && (IsBaz<C>), int> = 0>
void frobozzle(A, B, C);

可能有任意数量的requires条款,每个条款都应该有自己的enable_if_t条款。我使用了一个requires子句,但是我在这个过程中耗尽了我的C预处理器。

可以假设符合标准的预处理器,因为我不需要MSVC支持。

3 个答案:

答案 0 :(得分:12)

如果在语法中添加一组额外的括号,则可以在不限制“必需”子句的数量的情况下使用相对较少的宏:

template_((class A, class B, class C)
    (requires IsFoo<A> && IsBar<B>)
    (requires IsBaz<C>)
)
void frobozzle(A, B, C);

宏:

#define template_(...) template_impl_ADD_END(template_impl_LIST __VA_ARGS__) >

#define template_impl_ADD_END(...) template_impl_ADD_END2(__VA_ARGS__)
#define template_impl_ADD_END2(...) __VA_ARGS__ ## _END

#define template_impl_LIST(...) template<__VA_ARGS__, int dummy = 0 template_impl_LIST_1

#define template_impl_LIST_1(...) , std::enable_if_t<dummy == 0 && template_impl_REQUIRES(__VA_ARGS__), int> = 0 template_impl_LIST_2
#define template_impl_LIST_2(...) , std::enable_if_t<dummy == 0 && template_impl_REQUIRES(__VA_ARGS__), int> = 0 template_impl_LIST_1

#define template_impl_REQUIRES(...) (template_impl_REQUIRES_ ## __VA_ARGS__)
#define template_impl_REQUIRES_requires

#define template_impl_LIST_END
#define template_impl_LIST_1_END
#define template_impl_LIST_2_END

使用这些宏,上面的示例扩展为:

template <class A, class B, class C,
          int dummy = 0,
          std::enable_if_t<dummy == 0 && (IsFoo<A> && IsBar<B>), int> = 0,
          std::enable_if_t<dummy == 0 && (IsBaz<C>), int> = 0>

void frobozzle(A, B, C);

解释

考虑这些宏:

#define a(x) [x] b
#define b(x) [x] a

有了这些,这个:

a (1) (2) (3) (4)

将引起扩张的“连锁反应”如下:

a (1) (2) (3) (4)
[1] b (2) (3) (4)
[1] [2] a (3) (4)
[1] [2] [3] b (4)
[1] [2] [3] [4] a

预处理器中不允许递归,但这种类型的链式反应不是递归,因为宏的调用只发生在前一个扩展之后,而不是在期间,因为(不是部分扩张。 (虽然,请参阅https://wg21.link/cwg268

不幸的是,尽管这会很好地循环遍历(A)(B)(C)的序列,但它会在末尾留下一个额外的标记:两个使用过的宏之一的名称。我用来摆脱那个的技巧是用另一个宏调用来包装整个列表,它将在完全扩展后附加(使用concat运算符##_END,因此它将成为:

[1] [2] [3] [4] a_END

然后我们可以通过定义:

简单地摆脱这最后一个标记
#define a_END
#define b_END

如果我们无法包装整个列表,则无法知道我们何时到达最后一个元素。唯一发生的事情是最后ab遗留下来而后面没有(,这意味着它不会像a那样展开b是函数式宏。 (而且我们不能只定义ab以扩展为空,因为ab已经是宏,但是如果没有{(它们将不会展开1}}。)

为什么有两个宏?

当我们试图引起像上面这样的连锁反应时,只有一个像这样的宏:

#define a(x) [x] a

它不起作用:

a (1) (2) (3) (4)
[1] a (2) (3) (4) // Doesn't expand further

这是因为'(无限)递归保护'的工作原理:如果在扩展宏期间,会生成一个扩展宏名称的令牌,它被标记为'unexpandable',这意味着它永远不会再扩张。见http://eel.is/c++draft/cpp.rescan#2

这意味着展开的a被标记为“不可扩展”,我们的连锁反应在第一步之后停在那里。我们通过使用两个宏来解决此规则来避免这种情况:a(..)不会生成任何具有自己名称的令牌,而只会生成另一个宏b的名称。在a展开之前,b的扩展就在那里结束,因为在b之后还没有(,因为我们在'内部'扩展了一个。扩展完成后,我们不再“在'a内,重新检查令牌,并找到b的正确调用:b(..)。那个会再次生成一个名为a的令牌,但由于我们不再处于第一个a的扩展中,因此这个不会被标记为'不可扩展',而链反应仍在继续。

答案 1 :(得分:2)

好吧,这是一个快速而又肮脏的东西我掀起来,我认为你可以使用:

#include <iostream>

#define LIST (1)(2)(3)(4)

#define EAT2(list)
#define EAT(list) EAT2 list
#define KEEP(x) x EAT2(
#define STRINGIFY2(x) #x
#define STRINGIFY(x) STRINGIFY2(x)

#define HEAD(list) KEEP list )
#define TAIL(list) EAT(list)
int main()
{
    std::cout << STRINGIFY(HEAD(LIST)) << std::endl;
    std::cout << STRINGIFY(TAIL(LIST)) << std::endl;
}

基本上,你需要对如何调用宏感到棘手 举个例子:

HEAD((1)(2))

扩展为

KEEP (1)(2) )

扩展为

1 EAT2 ((2))

扩展为

1

这不是一个完整的答案,但我认为可以作为你想要做的事情的起点。

编辑

我现在已经弄清楚BOOST.PP如何进行迭代并且它不漂亮,你基本上手动写出迭代到一些最大尺寸。

#define CONCAT2(x, y) x##y
#define CONCAT(x, y) CONCAT2(x, y)

#define SEQ_SIZE(seq) CONCAT(SEQ_SIZE_, SEQ_SIZE_0 seq)

# define SEQ_SIZE_0(_) SEQ_SIZE_1
# define SEQ_SIZE_1(_) SEQ_SIZE_2
# define SEQ_SIZE_2(_) SEQ_SIZE_3
# define SEQ_SIZE_3(_) SEQ_SIZE_4
# define SEQ_SIZE_4(_) SEQ_SIZE_5
# define SEQ_SIZE_5(_) SEQ_SIZE_6
# define SEQ_SIZE_6(_) SEQ_SIZE_7
# define SEQ_SIZE_7(_) SEQ_SIZE_8
# define SEQ_SIZE_8(_) SEQ_SIZE_9
# define SEQ_SIZE_9(_) SEQ_SIZE_10
# define SEQ_SIZE_10(_) SEQ_SIZE_11
# define SEQ_SIZE_SEQ_SIZE_0 0
# define SEQ_SIZE_SEQ_SIZE_1 1
# define SEQ_SIZE_SEQ_SIZE_2 2
# define SEQ_SIZE_SEQ_SIZE_3 3
# define SEQ_SIZE_SEQ_SIZE_4 4
# define SEQ_SIZE_SEQ_SIZE_5 5
# define SEQ_SIZE_SEQ_SIZE_6 6
# define SEQ_SIZE_SEQ_SIZE_7 7
# define SEQ_SIZE_SEQ_SIZE_8 8
# define SEQ_SIZE_SEQ_SIZE_9 9
# define SEQ_SIZE_SEQ_SIZE_10 10

#define MAKE_VAR(elem)                         \
    float CONCAT(var_, elem) = 0;

#define MAKE_LIST_0(op, list)
#define MAKE_LIST_1(op, list)  op (HEAD(list)) MAKE_LIST_0(op, TAIL(list))
#define MAKE_LIST_2(op, list)  op (HEAD(list)) MAKE_LIST_1(op, TAIL(list))
#define MAKE_LIST_3(op, list)  op (HEAD(list)) MAKE_LIST_2(op, TAIL(list))
#define MAKE_LIST_4(op, list)  op (HEAD(list)) MAKE_LIST_3(op, TAIL(list))
#define MAKE_LIST_5(op, list)  op (HEAD(list)) MAKE_LIST_4(op, TAIL(list))
#define MAKE_LIST_6(op, list)  op (HEAD(list)) MAKE_LIST_5(op, TAIL(list))
#define MAKE_LIST_7(op, list)  op (HEAD(list)) MAKE_LIST_6(op, TAIL(list))
#define MAKE_LIST_8(op, list)  op (HEAD(list)) MAKE_LIST_7(op, TAIL(list))
#define MAKE_LIST_9(op, list)  op (HEAD(list)) MAKE_LIST_8(op, TAIL(list))
#define MAKE_LIST_10(op, list)  op (HEAD(list)) MAKE_LIST_9(op, TAIL(list))

#define MAKE_LIST(op, list) CONCAT(MAKE_LIST_, SEQ_SIZE(list)) (op, list)

int main()
{
    MAKE_LIST(MAKE_VAR, LIST)
}

在此上运行预处理器提供以下内容:

int main()
{
    float var_1 = 0; float var_2 = 0; float var_3 = 0; float var_4 = 0; float var_5 = 0;
}

根据需要。我确信这可以简化一下,但我希望这会有所帮助。

答案 2 :(得分:0)

这是我的小2美分:

我从Boost.Preprocessor中使用的预处理器元编程技术中记住的问题是,在折叠序列时,不可能有任意长的元素列表。

你需要拥有尽可能多的宏作为最大迭代次数,因此它可以是任意的,但要达到最大值。

我想知道你是否甚至可以扩展逗号,因为通常这是基于连接到停止条件宏或下一个迭代宏旁边的内容。而且我不知道如何通过连接宏扩展逗号是可能的,因为连接将不再起作用。

如果您可以更改API,我将为此案例做些什么:

#define EXPAND(...) __VA_ARGS__

#define template_(X, Y)   \
  template<EXPAND X       \
    , int dummy = 0       \
  Y                       \
>

#define requires(...) \
  COMMA() std::enable_if_t< dummy == 0 && (__VA_ARGS__) > = 0  

#define COMMA() ,

因此改变了一个简单的API:

template_((class A, class B, class C),
    requires(IsFoo<A> && IsBar<B>)
    requires(IsBaz<C>)
)
void frobozzle(A, B, C);

它输出到希望的内容:

template<class A, class B, class C , 
  int dummy = 0 , 
  std::enable_if_t< dummy == 0 && (IsFoo<A> && IsBar<B>) > = 0 ,
  std::enable_if_t< dummy == 0 && (IsBaz<C>) > = 0 >
void frobozzle(A, B, C);

不完全是请求的API,但优点是您可以要求使用逗号表达式,这要归功于使用 VA_ARGS 的需要宏:

template_((class A, class B, class C),
    requires(IsBaseOf<B,C>)
)
int boo()

我努力制作一个无限制的FOLD_LEFT,但它看起来并不是我能够达到的目标:D。

我没有尝试再现与您提供的表达式模板相同的输入语法,这听起来对我来说更加可行。