3

Given a macro FOO used like this:

std::string f1();
std::string f2();
FOO(f1().c_str(), f2().c_str());

Note: The type, std::string is only an example. FOO is generic and may not assume anything about types.

that is supposed to guarantee the order of evaluation of f1() and f2() by doing something like:

#define FOO(e1, e2) \
do {                \
    auto v1 = e1;   \
    auto v2 = e2;   \
    foo(e1, e2);    \
} while(0)

Edit: unfortunately foo can also be a template.

Unfortunately, that way the temporary returned by f1 is deleted and the c_str becomes invalid for the call to foo.

Is there a way to guarantee the order of expression evaluation for macro parameters while retaining all temporary lifetimes?

Surely there are overall better ways to approach that, but I'm specifically curious if there is a way to do this without reasoning about each of the usages of that macro in a large code base. Further I want to avoid handling specific types (i.e. not preserving const char* with strdup).

Zulan
  • 21,896
  • 6
  • 49
  • 109

2 Answers2

4

This is trivial in C++17, using std::apply:

#define FOO(...) std::apply(foo, decltype(std::forward_as_tuple(__VA_ARGS__)){__VA_ARGS__})

Example.

If you're using a pre-C++17 standard library, you can use the recipe for implementing std::apply at the above link.

If foo is a function template or an overload set, it can't be passed directly to std::apply so it has to be 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__})

This works because the order of evaluation within {} braces is strict left-to-right. We use std::forward_as_tuple to determine the type of the tuple we want to pass to apply, but we construct it using list-initialization syntax.

If you're using a C++17 compiler with class template argument deduction, and don't need to worry about lvalue references, you could simplify this even further:

#define FOO(...) std::apply(foo, std::tuple{__VA_ARGS__})

Unfortunately, because the solution (without class template argument deduction) uses decltype, it won't work if the arguments involve lambda expressions. The only way I can see to make it work in this case is to use the sequencing between function arguments and function body, expanding FOO(e1, e2) to:

[&](auto&& p1) {
    return [&](auto&& p2) {
        return foo(std::forward<decltype(p1)>(p1), std::forward<decltype(p2)>(p2));
    }(e2);
}(e1)

This is actually possible using the incredible Boost.Preprocessor library:

#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.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • This doesn't keep around the temporaries like OP would like (not saying it's a good idea). – TartanLlama Aug 10 '17 at 10:34
  • @TartanLlama they're preserved for the duration of the call, which is all OP needs. – ecatmur Aug 10 '17 at 10:34
  • oh my bad, I thought they were storing the pointers somewhere or something crazy like that. – TartanLlama Aug 10 '17 at 10:35
  • It'd be nice if there was some slight modification to the latter syntax that supported forwarding, to avoid the hefty decltype in the first one – M.M Aug 10 '17 at 11:12
  • Unfortunately I have learned that `foo` can also be a template in the code, which seems to be a problem with `std::apply` *sigh*. – Zulan Aug 10 '17 at 12:34
  • @Zulan that's OK, you need to lift it into a polymorphic lambda. I'll update my answer. – ecatmur Aug 10 '17 at 12:55
  • Woha, thanks alot! I tried a lambda but didn't get it right. Now it works in my small test, but the production code crashes gcc 7.1.1 with a segfault (and it's not just the stack limit). I'll try to make a distilled example as gcc bugreport. – Zulan Aug 10 '17 at 13:50
  • I hate to say this. But in a dark corner of the production code, there is a lambda within the expression passed to `FOO(..., std::count_if(... , [](){}))` which gives a *lambda-expression in unevaluated context*. I'll try to instantiate a helper struct within the macro to pass in the `function`. – Zulan Aug 10 '17 at 19:54
  • @Zulan Came up with a preprocessor-heavy nested-lambda solution. – ecatmur Aug 10 '17 at 21:42
  • 1
    As OP apparently doesn't care about the result (note the `do ... while(0)`), we can just use a class type whose constructor does the calling and list-initialize that from `{ lambda-wrapping-foo, __VA_ARGS__ }` – T.C. Aug 10 '17 at 21:53
0

Generalized solution:

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

The reason why it has to work: evaluation order of braced-init-list is guaranteed.

C++11 Standard, 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...

Unlike the accepted solution it works with C++11 without any shenanigans and do not need Boost.

WindyFields
  • 2,697
  • 1
  • 18
  • 21
  • 2
    If you give a minus, please be so kind to explain. This is such a stack-overflow style... – WindyFields Aug 10 '17 at 10:25
  • 3
    The declarations of f1 and f2 are outside the do...while, and you don't answer the question. You just say "what is the purpose of this code?" and suggest different code. That question should be a comment. – M.M Aug 10 '17 at 11:04
  • 1
    OK. I think OP wants the arguments to be any arbitrary expression involving a temporary , where the first one needs to be called before the second one. Your suggestion works for `std::string` only. – M.M Aug 10 '17 at 11:10
  • But my previous answers used to work for strings only – WindyFields Aug 16 '17 at 09:22
  • @DevNull the answer was completely changed since I made that comment – M.M Aug 16 '17 at 22:03