Подтвердить что ты не робот

Как гарантировать порядок оценки аргументов при вызове функционального объекта?

Ответы на вопрос как избежать выполнения заказа undefined для конструкторов при использовании std:: make_tuple привели к обсуждению, в ходе которого я узнал, что порядок конструктивных аргументов может быть гарантирован для конструкторов: с помощью списка braced-init список будет гарантированно оставлен вправо:

T{ a, b, c }

Выражения a, b и c оцениваются в данном порядке. Это имеет место, даже если тип T имеет только определенный конструктор.

Ясно, что не все, что называется, является конструктором, и иногда было бы неплохо гарантировать порядок оценки при вызове функции, но нет такой вещи, как brace-argument-list для вызова функции с определенным порядком оценки их аргументы. Возникает вопрос: могут ли гарантии для конструкторов использовать функцию вызова функций ( "function_apply()" ) с гарантией заказа для оценки аргументов? Допустимо требовать вызова функционального объекта.

4b9b3361

Ответ 1

Как насчет такого глупого класса обертки:

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

Использование:

void foo(int, char, bool);

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

Если вы хотите получить возвращаемое значение, вы можете передать его по ссылке (вам понадобится некоторый признак для извлечения возвращаемого типа) или сохранить его в объекте, чтобы получить интерфейс, например:

auto x = OrderedCall{foo, 5, 'x', false}.get_result();

Ответ 2

В решении, которое я придумал, используется std::tuple<...> для объединения аргументов, а не для вызова объекта функции с использованием элементов этого объекта. Преимущество состоит в том, что он может выводить возвращаемый тип. Фактическая конкретная логика выглядит следующим образом:

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>());
}

... который вызывается с помощью выражения, подобного этому:

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() });
}

Основной недостаток заключается в том, что для этого подхода требуется спецификация элементов std::tuple<...>. Другая проблема заключается в том, что текущая версия gcc на MacOS вызывает функции в противоположном порядке, которые они отображают, т.е. Порядок оценки в скобках-init-list не соблюдается (ошибка gcc) или не существует (т.е., Я неправильно понял, что вы используете список braced-init. Clang на той же платформе выполняет функции в ожидаемом порядке.

Используемая функция make_indices() просто создает подходящий указатель на объект типа indices<I...> со списком индексов, который можно использовать с 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;
}

Ответ 3

Во-первых, я думаю, что если порядок имеет значение, вероятно, лучше всего явно создать эти элементы перед вызовом, а затем передать их. Намного легче читать, но гораздо менее забавно!

Это только расширение ответа Kerrek:

#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__)

И пример:

#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() };
}

Эта последняя строка для проверки инициализаторов скобок действительно имеет эффект, обычно.

К сожалению, как было обнаружено из других ответов/комментариев, GCC не понимает это правильно, поэтому я не могу проверить свой ответ. Кроме того, MSVC Nov2012CTP также не делает это правильно (и имеет неприятную ошибку, которая задыхается на ordered_call_result †). Если кто-то хочет проверить это с помощью clang, это будет набухать.

† Для этого конкретного примера возвращаемый тип возврата может быть decltype(f(0, 0, 0)).

Ответ 4

Могут ли гарантии для конструкторов использоваться для создания объекта вызова функции ( "function_apply()" ) с гарантией заказа для оценки аргументов?

Да, библиотека Fit уже делает это с помощью fit::apply_eval:

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

Так foo() будет вызываться до bar().