20

The answers to the question on how to avoid undefined execution order for the constructors when using std::make_tuple led to a discussion during which I learned that the order of argument evaluation can be guaranteed for constructors: Using a braced-init-list the order is guaranteed to be left to right:

T{ a, b, c }

The expressions a, b, and c are evaluated in the given order. This is the case, even if the type T just has a normal constructor defined.

Clearly, not everything called is a constructor and sometimes it would be nice to guarantee the order of evaluation when calling a function but there is no such thing as brace-argument-list to call function with a defined order of evaluation of their arguments. The question becomes: Can the guarantees to constructors be used to build a function call facility ("function_apply()") with an ordering guarantee for the evaluation of arguments? It is acceptable to require a function object being called.

Community
  • 1
  • 1
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • There is an argument, for wherever it makes sense, evaluating which argument requires evaluation first into a variable and then passing it into the call. For situations where the wrapper object is too heavyweight or where you want to make the dependancy more visible to a casual glance at the code. – Will Dec 27 '12 at 17:29
  • 1
    We had fun with this in the chat some time ago http://chat.stackoverflow.com/transcript/message/6383436#6383436 :) – Johannes Schaub - litb Dec 27 '12 at 17:33

4 Answers4

15

What about a silly wrapper class like this:

struct OrderedCall
{
    template <typename F, typename ...Args>
    OrderedCall(F && f, Args &&... args)
    {
        std::forward<F>(f)(std::forward<Args>(args)...);
    }
};

Usage:

void foo(int, char, bool);

OrderedCall{foo, 5, 'x', false};

If you want a return value, you could pass it in by reference (you'll need some trait to extract the return type), or store it in the object, to get an interface like:

auto x = OrderedCall{foo, 5, 'x', false}.get_result();
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • This looks actually pretty nice! I was thinking about creating a `std::tuple<...>` first but it seems there is no need to create `std::tuple<...>` which would require spelling out the argument names. – Dietmar Kühl Dec 27 '12 at 17:33
  • Implementing `get_result` is going to be difficult because you don't know the return type nor the function object's type when `OrderedCall` is defined :( – Johannes Schaub - litb Dec 27 '12 at 17:38
  • 2
    Interestingly, using `OrderedCall{&foo, fi(), fd(), fb() };` on MacOS calls `fb()`, then `fd()`, and finally `fi()` using gcc. With clang the order is, as expected, `fi()`, `fd()`, and `fb()`. – Dietmar Kühl Dec 27 '12 at 17:39
  • Why does that guarantee a specific order of evaluation? @DietmarKühl I remember a compiler that shipped with a UNIX way back when that always evaluated function parameters last-to-first, without exception. I do not even know if the compiler I am using now has a preferred order. – Jive Dadson Dec 27 '12 at 17:46
  • You could call a function object with the result. And then pass a phoenix function object `OrderedCall{foo, phoenix::var(x) = phoenix::arg1, 5, 'x', false};`. When we have polymorphic lambda expressions, things will be fun `OrderedCall{foo, [&](auto &&x) { ... }, 5, 'x', false}`. – Johannes Schaub - litb Dec 27 '12 at 17:47
  • 4
    @JiveDadson: A *braced-init-list* does guarantee the order of evaluation according to 12.6.1 [class.explicit.init] paragraph 2 and 8.5.4 [dcl.init.list] paragraph 4. ... and I doubt that an old compiler would conform to C++ 2011. – Dietmar Kühl Dec 27 '12 at 17:53
  • Braced init list to class that implicitly converts to tuple somehow? Doing this unsafely does not look hard. – Yakk - Adam Nevraumont Dec 27 '12 at 20:44
  • 1
    @JohannesSchaub-litb: I haven't thought too deeply about this, but wouldn't the function type be deduced, and then you can use `decltype(f(std::declval()...))` as the return type? – Kerrek SB Dec 28 '12 at 00:10
  • 1
    @DietmarKühl it's also doubtful that a pre-C++11 compiler would support C++11 list initialization syntax, so if `OrderedCall{......}` compiles, it should be expected to work – M.M Dec 18 '14 at 03:56
5

The solution I had come up uses std::tuple<...> to put the arguments together than calls a function object using the elements of this object. The advantage is that it can deduce the return type. The actual specific logic looks like this:

template <typename F, typename T, int... I>
auto function_apply(F&& f, T&& t, indices<I...> const*)
    -> decltype(f(std::get<I>(t)...)) {
    f(std::get<I>(t)...);
}

template <typename F, typename T>
auto function_apply(F&& f, T&& t)
    -> decltype(function_apply(std::forward<F>(f), std::forward<T>(t),
                               make_indices<T>())) {
    function_apply(std::forward<F>(f), std::forward<T>(t),
                   make_indices<T>());
}

... which is called using an expression like this:

void f(int i, double d, bool b) {
    std::cout << "i=" << i << " d=" << d << " b=" << b << '\n';
}

int fi() { std::cout << "int\n"; return 1; }
double fd() { std::cout << "double\n"; return 2.1; }
bool fb() { std::cout << "bool\n"; return true; }

int main()
{
    std::cout << std::boolalpha;
    function_apply(&f, std::tuple<int, double, bool>{ fi(), fd(), fb() });
}

The main disadvantage is that this approach requires the specification of the std::tuple<...>'s elements. Another problem is that the current version of gcc on MacOS calls the functions in the opposite order they appear, i.e., the order of evaluation in a braced-init-list isn't obeyed (a gcc bug) or doesn't exist (i.e., I misunderstood the guarantees of using a braced-init-list. clang on the same platform executes the functions in the expected order.

The used function make_indices() just creates a suitable pointer to an object of type indices<I...> with a list of indices usable with a std::tuple<...>:

template <int... Indices> struct indices;
template <> struct indices<-1> { typedef indices<> type; };
template <int... Indices>
struct indices<0, Indices...>
{
    typedef indices<0, Indices...> type;
};
template <int Index, int... Indices>
struct indices<Index, Indices...>
{
    typedef typename indices<Index - 1, Index, Indices...>::type type;
};

template <typename T>
typename indices<std::tuple_size<T>::value - 1>::type const*
make_indices()
{
    return 0;
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
1

First, I think if the order does matter it's probably best to explicitly construct those elements prior to the call, then pass them in. Much easier to read, but far less fun!

This is just expanding on Kerrek's answer:

#include <utility>

namespace detail
{
    // the ultimate end result of the call;
    // replaceable with std::result_of? I do not know.
    template <typename F, typename... Args>
    static auto ordered_call_result(F&& f, Args&&... args)
        -> decltype(std::forward<F>(f)
                    (std::forward<Args>(args)...)); // not defined

    template <typename R>
    class ordered_call_helper
    {
    public:
        template <typename F, typename... Args>
        ordered_call_helper(F&& f, Args&&... args) :
        mResult(std::forward<F>(f)(std::forward<Args>(args)...))
        {}

        operator R()
        {
            return std::move(mResult);
        }

    private:
        R mResult;
    };

    template <>
    class ordered_call_helper<void>
    {
    public:
        template <typename F, typename... Args>
        ordered_call_helper(F&& f, Args&&... args)
        {
            std::forward<F>(f)(std::forward<Args>(args)...);
        }
    };

    // perform the call then coax out the result member via static_cast,
    // which also works nicely when the result type is void (doing nothing)
    #define ORDERED_CALL_DETAIL(r, f, ...) \
            static_cast<r>(detail::ordered_call_helper<r>{f, __VA_ARGS__})
};

// small level of indirection because we specify the result type twice
#define ORDERED_CALL(f, ...) \
        ORDERED_CALL_DETAIL(decltype(detail::ordered_call_result(f, __VA_ARGS__)), \
                            f, __VA_ARGS__)

And an example:

#include <iostream>

int add(int x, int y, int z)
{
    return x + y + z;
}

void print(int x, int y, int z)
{
    std::cout << "x: " << x << " y: " << y << " z: " << z << std::endl;
}

int get_x() { std::cout << "[x]"; return 11; }
int get_y() { std::cout << "[y]"; return 16; }
int get_z() { std::cout << "[z]"; return 12; }

int main()
{
    print(get_x(), get_y(), get_z());
    std::cout << "sum: " << add(get_x(), get_y(), get_z()) << std::endl;

    std::cout << std::endl;   

    ORDERED_CALL(print, get_x(), get_y(), get_z());
    std::cout << "sum: " << ORDERED_CALL(add, get_x(), get_y(), get_z()) << std::endl;

    std::cout << std::endl;

    int verify[] = { get_x(), get_y(), get_z() };
}

That last line is there to verify brace initializers do in fact have effect, normally.

Unfortunately as has been discovered from other answers/comments, GCC does not get it right so I cannot test my answer. Additionally, MSVC Nov2012CTP also does not get it right (and has a nasty bug that chokes on the ordered_call_result†). If someone wants to test this with clang, that would be swell.

†For this particular example, the trailing return type can be decltype(f(0, 0, 0)) instead.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • I generally agree that there are often better ways then rely on the order of evaluation within an expression. When getting a variadic list of arguments putting a temporary for each argument on the stack isn't an option, however. It *is* possible to put a `std::tuple<...>` with the arguments on the stack, though, and then expand that to get the function called (a two-stage alternative of calling the function directly). – Dietmar Kühl Dec 27 '12 at 19:10
0

Can the guarantees to constructors be used to build a function call facility ("function_apply()") with an ordering guarantee for the evaluation of arguments?

Yes, the Fit library already does this with fit::apply_eval:

 auto result = fit::apply_eval(f, [&]{ return foo() }, [&]{ return bar(); });

So foo() will be called before bar().

Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59