19

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.

4

4 に答える 4

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();
于 2012-12-27T17:25:16.087 に答える
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;
}
于 2012-12-27T18:02:14.077 に答える
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.

于 2012-12-27T18:59:39.527 に答える
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().

于 2015-06-01T23:41:19.983 に答える