BOOST_PP_SEQ_FOLD_LEFT如何工作

How does BOOST_PP_SEQ_FOLD_LEFT work?

本文关键字:何工作 工作 LEFT PP SEQ FOLD BOOST      更新时间:2023-10-16

我需要编写一个宏来处理任意长的东西列表,如(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 预处理器-fu。

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

如果在语法中添加一组额外的括号,则可以对"必需"子句的数量没有限制,并且宏相对较少:

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被保留下来,没有紧随其后的(,这意味着它根本不会扩展,因为ab是函数样式的宏。(而且我们不能只定义ab扩展到无,因为ab已经是宏,尽管它们不会在没有(的情况下扩展。

为什么是两个宏?

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

#define a(x) [x] a

它不起作用:

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

这是因为"(无限)递归保护"的工作原理:如果在宏的扩展过程中,生成了一个具有正在扩展的宏名称的令牌,它被标记为"不可扩展",这意味着它永远不会再扩展。请参阅 http://eel.is/c++draft/cpp.rescan#2

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

好吧,这是我快速而肮脏的东西,我认为你可以使用:

#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

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

编辑

我现在已经弄清楚了如何提升。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美分:

我记得 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范围内。

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