Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perfect forwarding with a temporary function wrapper

Consider the following code in C++14, following the posts here, here and here:

// 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;
}
*/

The goal is to produce the following call syntax:

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

Where f0, f1, f2 are either function pointers, functors, lambdas..., and where a, b, c, d, e are arguments to be perfectly forwarded. This function should produce a temporary type, and then call the operator() of that temporary type, and this operator should do a perfect forwarding of fn (for now f0, it will be changed later) with the arguments a, b, c, d, e.... The temporary_function_queue should not be usable in any other context.

The problem is that I am a little lost with the forwarding, universal references and lvalue references... Is the code shown above safe? If not what example of use would lead to undefined behaviour? And in that case, how to make it safe, and efficient (ideally, I would like no runtime overhead on most compilers with -O3)?

like image 879
Vincent Avatar asked Nov 08 '22 02:11

Vincent


1 Answers

Note: Dangling references are quite likely to happen with this approach.

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

First some remarks about wording and deduction. Let:

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

Note: When this template is instantiated, there are two different packs here: T... and T&&....

Call it with an lvalue or type R and an rvalue of type Q:

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

Now T... will be R&, Q but T&&... will be R&, Q&&.

Forwarding the pack p will result in the T&&... pack.

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

(Note: You can't actually apply decltype here - it's just for illustration.)

Therefore, I'll call the pack that is actually deduced (T...) the deduced types/pack and the result of adding rvalue references / forwarding (T&&...) the forwarded types/pack.


Applying && everywhere inside the class as well as in the return type of apply_function_queue is superfluous. (If you return temporary_function_queue<F&&...> from apply_function_queue there is no need for && inside temporary_function_queue. And if you apply && inside the class everywhere there is no need to return temporary_function_queue<F&&...>.)

You either instantiate the class template with the deduced pack and add && everywhere you want references our you instantiate the class template with the forwarding pack and don't add &&.

It is required to have the deduced types available in the class. (Because the friend declaration uses both F... and F&&....) So you'll want to remove && from the return type of apply_function_queue.

You'll need to change some declarations:

apply_function_queue

forward declaration:

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

definition:

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

temporary_type

The class instance type is temporary_function_queue<F...> not temporary_function_queue<F&&...>!

using temporary_type = temporary_function_queue<F...>;

friend declaration

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

If you want perfect forwarding of rvalue function types in the function call operator you'll have to resort to manual casting / forwarding I think.

Inside decltype(auto) operator()(Args&&... args) const&& you'll find that

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

which by reference collapsing rules is a lvalue reference. What you actually want, in order to forward the elements from the tuple is the tuple_element::type.

Thus you'd have to directly cast to the actual type in the tuple:

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

or forward (which will have the same effect through reference collapsing):

return std::forward<std::tuple_element_t<0u, data_type>>(
    std::get<0>(_f))(std::forward<Args>(args)...);
like image 117
Pixelchemist Avatar answered Nov 14 '22 21:11

Pixelchemist