给定像这样使用的宏FOO
:
std::string f1();
std::string f2();
FOO(f1().c_str(), f2().c_str());
注意:类型std::string
只是一个例子。 FOO
是通用的,可能不会假设任何类型。
应该通过执行以下操作来保证f1()
和f2()
的评估顺序:
#define FOO(e1, e2) \
do { \
auto v1 = e1; \
auto v2 = e2; \
foo(e1, e2); \
} while(0)
编辑:不幸的是foo
也可以是模板。
不幸的是,f1
返回的临时文件被删除,c_str
对foo
的调用无效。
有没有办法保证宏参数的表达式评估顺序,同时保留所有临时生命周期?
当然有更好的方法可以解决这个问题,但我特别好奇是否有办法在没有推理大型代码库中的宏的每个用法的情况下做到这一点。此外,我希望避免处理特定类型(即不使用const char*
保留strdup
)。
答案 0 :(得分:4)
使用std::apply
:
#define FOO(...) std::apply(foo, decltype(std::forward_as_tuple(__VA_ARGS__)){__VA_ARGS__})
如果您使用的是预C ++ 17标准库,则可以使用该配方在上述链接中实现std::apply
。
如果foo
是功能模板或重载集,则无法将其直接传递给std::apply
,因此必须为wrapped in a polymorphic lambda:
#define FOO(...) std::apply( \
[](auto&&... args) -> decltype(auto) { return foo(std::forward<decltype(args)>(args)...); }, \
decltype(std::forward_as_tuple(__VA_ARGS__)){__VA_ARGS__})
这是有效的,因为{}
括号内的评估顺序是从左到右严格的。我们使用std::forward_as_tuple
来确定我们想要传递给apply
的元组的类型,但我们使用列表初始化语法来构造它。
如果您正在使用带有类模板参数推导的C ++ 17编译器,并且不需要担心左值引用,则可以进一步简化:
#define FOO(...) std::apply(foo, std::tuple{__VA_ARGS__})
不幸的是,因为解决方案(没有类模板参数推导)使用decltype
,如果参数涉及lambda表达式,它将无法工作。在这种情况下,我能看到使其工作的唯一方法是使用函数参数和函数体之间的排序,将FOO(e1, e2)
扩展为:
[&](auto&& p1) {
return [&](auto&& p2) {
return foo(std::forward<decltype(p1)>(p1), std::forward<decltype(p2)>(p2));
}(e2);
}(e1)
实际上可以使用令人难以置信的Boost.Preprocessor库:
#define FOO_IMPL_START(z,n,_) [&](auto&& p ## n) { return
#define FOO_IMPL_PARAM(z,n,_) std::forward<decltype(p ## n)>(p ## n)
#define FOO_IMPL_END(z,n,t) ; }(BOOST_PP_TUPLE_ELEM(n,t))
#define FOO_IMPL(n,t) \
BOOST_PP_REPEAT(n, FOO_IMPL_START, _) \
foo(BOOST_PP_ENUM(n, FOO_IMPL_PARAM, _)) \
BOOST_PP_REPEAT(n, FOO_IMPL_END, BOOST_PP_TUPLE_REVERSE(t))
#define FOO(...) (FOO_IMPL(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), BOOST_PP_VARIADIC_TO_TUPLE(__VA_ARGS__)))
答案 1 :(得分:0)
广义解决方案:
struct Execute
{
template <typename Func, typename ... Args>
Execute(Func&& function, Args&& ... args)
{
std::forward<Func>(function)(std::forward<Args>(args) ...);
}
};
#define FOO(...) \
do { \
Execute{foo, __VA_ARGS__}; \
} while(0)
FOO(f1().c_str(), f2().c_str()); // this will be evaluated in order from left to right
它必须工作的原因:保证了braced-init-list的评估顺序。
C ++ 11 Standard,8.5.4 / 4:
在braced-init-list的初始化列表中, initializer-clause,包括包扩展产生的任何条款 (14.5.3),按照它们出现的顺序进行评估......
与公认的解决方案不同,它适用于 C ++ 11 ,没有任何恶作剧,也不需要Boost。