Идеальная переадресация с временной оболочкой функции

Рассмотрим следующий код в C++14, следуя сообщениям здесь, здесь и здесь:

// Include
#include <tuple>
#include <iostream>
#include <type_traits>

// Temporary function queue declaration
template <class... F>
class temporary_function_queue;

// Apply function queue declaration
template <class... F>
constexpr temporary_function_queue<F&&...> apply_function_queue(F&&... f);

// Temporary function queue definition
template <class... F>
class temporary_function_queue final
{
    // Types
    private:
    using const_lvalue_reference = const temporary_function_queue&;
    using rvalue_reference = temporary_function_queue&&;
    using temporary_type = temporary_function_queue<F&&...>;
    using data_type = std::tuple<F&&...>;

    // Lifecycle
    private:
    temporary_function_queue(rvalue_reference) = default;
    temporary_function_queue(const_lvalue_reference) = delete;
    temporary_function_queue operator=(rvalue_reference) = delete;
    temporary_function_queue operator=(const_lvalue_reference) = delete;
    explicit constexpr temporary_function_queue(F&&... f)
    : _f{std::forward<F>(f)...}
    {
    }

    // Temporary creator
    public:
    friend constexpr temporary_type apply_function_queue<>(F&&... f);

    // Apply function queue declaration
    public:
    template <class... Args> 
    decltype(auto) operator()(Args&&... args) const&&
    {
        // Do I need to do std::forward on f0 too? If so, how?
        return std::get<0>(_f)(std::forward<Args>(args)...);
    }

    // Data members
    private:
    data_type _f;
};

// Apply function queue definition
template <class... F>
constexpr temporary_function_queue<F&&...> apply_function_queue(F&&... f)
{
    return temporary_function_queue<F&&...>(std::forward<F>(f)...);
}

/* Example of use
int main(int argc, char* argv[])
{
    apply_function_queue(
        [](auto i){std::cout<<0<<std::endl;},
        [](auto i){std::cout<<1<<std::endl;}
    )(0);
    return 0;
}
*/

Цель состоит в том, чтобы создать следующий синтаксис вызова:

apply_function_queue(f0, f1, f2)(a, b, c, d, e);

Где f0, f1, f2 — это либо указатели на функции, функторы, лямбда-выражения..., а где a, b, c, d, e — аргументы, которые должны быть идеально перенаправлены. Эта функция должна создавать временный тип, а затем вызывать operator() этого временного типа, и этот оператор должен выполнять идеальную пересылку fn (сейчас f0, он будет изменен позже) с аргументами a, b, c, d, e.... temporary_function_queue нельзя использовать ни в каком другом контексте.

Проблема в том, что я немного запутался с переадресацией, универсальными ссылками и ссылками lvalue... Безопасен ли приведенный выше код? Если нет, то какой пример использования приведет к неопределенному поведению? И в таком случае, как сделать его безопасным и эффективным (в идеале я бы хотел, чтобы на большинстве компиляторов с -O3 не было накладных расходов во время выполнения)?


person Vincent    schedule 03.02.2017    source источник
comment
Хранение функции может стать накладным расходом. Какова ваша конечная цель с apply_function_queue()? Подать заявку f0(f1(f2(a, b, c, d, e)))? или {f0(a, b, c, d, e), f1(a, b, c, d, e), f2(a, b, c, d, e)}?   -  person lorro    schedule 04.02.2017
comment
Конечная цель — применить первую функцию f, чтобы f(a, b, c, d, e...) было допустимым выражением (см. сообщения, перечисленные в первой строке).   -  person Vincent    schedule 04.02.2017
comment
@Т.С. Что ты имеешь в виду?   -  person Vincent    schedule 04.02.2017
comment
using data_type = std::tuple<F&&...>; Это создаст кортеж идеально пересылаемых ссылок l или r-value. Не то, что вы хотите. Вы хотите отмыть это через std::remove_reference.   -  person Sam Varshavchik    schedule 04.02.2017
comment
@T.C.: О, я только что понял, что ты имеешь в виду   -  person Vincent    schedule 04.02.2017
comment
@SamVarshavchik: зачем мне это отмывать? В каком случае std::tuple<F&&...> приведет к неопределенному поведению?   -  person Vincent    schedule 04.02.2017
comment
В случае, когда объект, который изначально был передан по ссылке, вышел за пределы области действия и был уничтожен до того, как была использована теперь оборванная ссылка. Очень легко, чтобы это произошло.   -  person Sam Varshavchik    schedule 04.02.2017
comment
Вы просто пытаетесь заново изобрести boost::hana::overload_linearly?   -  person T.C.    schedule 04.02.2017
comment
@SamVarshavchik: Суть в идеальной пересылке / разрешении только временных файлов temporary _function_queue. Хранение по значению будет другим подходом (т.е. вы можете разрешить копирование/перемещение по умолчанию и т.д.).   -  person Pixelchemist    schedule 04.02.2017
comment
@ Винсент: если бы вашей целью было просто позвонить f(a, b, c, d, e), вы бы просто позвонили. Поэтому я спросил: какова конечная цель, какова цель класса.   -  person lorro    schedule 04.02.2017


Ответы (1)


Примечание. При таком подходе вполне вероятны висячие ссылки.

auto make_f(); // return by value
auto&& q = apply_function_queue(make_f());
// q holds dangling rvalue reference
q(a, b, c); // whoops...

Сначала несколько замечаний о формулировке и дедукции. Позволять:

template<class... T> void f(T&&... p) {}

Примечание. При создании экземпляра этого шаблона здесь есть два разных пакета: T... и T&&....

Вызовите его с lvalue или типом R и rvalue типа Q:

R a;
f(a, Q{});

Теперь T... будет R&, Q, а T&&... будет R&, Q&&.

Пересылка пакета p приведет к пакету T&&....

'decltype'(std::forward<T>(p)...) === T&&...

(Примечание: на самом деле вы не можете применить здесь decltype — это просто для иллюстрации.)

Поэтому я буду называть фактически выведенный пакет (T...) выведенными типами/пакетом, а результат добавления ссылок rvalue/переадресации (T&&...) перенаправленными типами/пакетом. >.


Применение && везде внутри класса, а также в возвращаемом типе apply_function_queue излишне. (Если вы возвращаете temporary_function_queue<F&&...> из apply_function_queue, нет необходимости в && внутри temporary_function_queue. А если вы применяете && внутри класса везде, нет необходимости возвращать temporary_function_queue<F&&...>.)

Вы либо создаете экземпляр шаблона класса с выведенным пакетом и добавляете && везде, где вы хотите ссылаться, либо вы создаете экземпляр шаблона класса с пакетом пересылки и не добавляете &&.

Требуется, чтобы выведенные типы были доступны в классе. (Поскольку в объявлении друга используются как F..., так и F&&....) Таким образом, вы захотите удалить && из возвращаемого типа apply_function_queue.

Вам нужно будет изменить некоторые объявления:

apply_function_queue

предварительная декларация:

template <class... F>
constexpr temporary_function_queue<F...> apply_function_queue(F&&... f);

определение:

template <class... F>
constexpr temporary_function_queue<F...> apply_function_queue(F&&... f)
{
    return temporary_function_queue<F...>(std::forward<F>(f)...);
}

temporary_type

Тип экземпляра класса — temporary_function_queue<F...>, а не temporary_function_queue<F&&...>!

using temporary_type = temporary_function_queue<F...>;

объявление друга

friend constexpr temporary_type apply_function_queue<F...>(F&&... f);

Если вам нужна идеальная переадресация типов функций rvalue в операторе вызова функции, вам придется прибегнуть к ручному приведению/пересылке, я думаю.

Внутри decltype(auto) operator()(Args&&... args) const&& вы найдете это

decltype(std::get<0>(_f)) === std::tuple_element_t<0u, data_type>&

который по правилам свертывания ссылок является ссылкой lvalue. Что вам на самом деле нужно для пересылки элементов из кортежа, так это tuple_element::type.

Таким образом, вам нужно будет напрямую привести фактический тип в кортеже:

return static_cast<std::tuple_element_t<0u, data_type>>(
    std::get<0>(_f))(std::forward<Args>(args)...);

или вперед (что будет иметь тот же эффект при свертывании ссылок):

return std::forward<std::tuple_element_t<0u, data_type>>(
    std::get<0>(_f))(std::forward<Args>(args)...);
person Pixelchemist    schedule 05.02.2017