在保留表达式生命周期的同时强制执行参数评估顺序

时间:2017-08-10 09:54:42

标签: c++ c++14 lifetime temporary-objects

给定像这样使用的宏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_strfoo的调用无效。

有没有办法保证宏参数的表达式评估顺序,同时保留所有临时生命周期?

当然有更好的方法可以解决这个问题,但我特别好奇是否有办法在没有推理大型代码库中的宏的每个用法的情况下做到这一点。此外,我希望避免处理特定类型(即不使用const char*保留strdup)。

2 个答案:

答案 0 :(得分:4)

使用std::apply

在C ++ 17中这是微不足道的
#define FOO(...) std::apply(foo, decltype(std::forward_as_tuple(__VA_ARGS__)){__VA_ARGS__})

Example

如果您使用的是预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__)))

Example

答案 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。