强制执行参数评估顺序,同时保留表达式的生命周期
Enforce parameter evaluation order while retaining lifetime of of expressions
给定一个宏 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
)。
通用解决方案:
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 标准,8.5.4/4:
Within the initializer-list of a braced-init-list, the
initializer-clauses, including any that result from pack expansions
(14.5.3), are evaluated in the order in which they appear...
与公认的解决方案不同,它与 C++11 一起工作,没有任何恶作剧,也不需要 Boost。
这在 C++17 中很简单,使用 std::apply
:
#define FOO(...) std::apply(foo, decltype(std::forward_as_tuple(__VA_ARGS__)){__VA_ARGS__})
如果您使用的是 C++17 之前的标准库,则可以使用上述 link.
中实现 std::apply
的方法
如果foo
是函数模板或者重载集,不能直接传给std::apply
所以必须是:
#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
的元组的 类型 ,但是我们使用列表初始化语法构造它。
如果您使用带有 class 模板参数推导的 C++17 编译器,并且不需要担心左值引用,您可以进一步简化:
#define FOO(...) std::apply(foo, std::tuple{__VA_ARGS__})
不幸的是,因为解决方案(没有 class 模板参数推导)使用 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__)))
给定一个宏 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
)。
通用解决方案:
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 标准,8.5.4/4:
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear...
与公认的解决方案不同,它与 C++11 一起工作,没有任何恶作剧,也不需要 Boost。
这在 C++17 中很简单,使用 std::apply
:
#define FOO(...) std::apply(foo, decltype(std::forward_as_tuple(__VA_ARGS__)){__VA_ARGS__})
如果您使用的是 C++17 之前的标准库,则可以使用上述 link.
中实现std::apply
的方法
如果foo
是函数模板或者重载集,不能直接传给std::apply
所以必须是
#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
的元组的 类型 ,但是我们使用列表初始化语法构造它。
如果您使用带有 class 模板参数推导的 C++17 编译器,并且不需要担心左值引用,您可以进一步简化:
#define FOO(...) std::apply(foo, std::tuple{__VA_ARGS__})
不幸的是,因为解决方案(没有 class 模板参数推导)使用 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__)))