Universal references and local classes

In my code below, I have a function that accepts a "universal reference" ( F&&

). The function also has an inner class that takes an object F&&

in its constructor. Is there F&&

still a universal reference at this point? That is, is F

it still considered an inferred type?

In other words, should I use std::forward<F>

or std::move

in a constructor initialization list?

#include "tbb/task.h"
#include <iostream>
#include <future>

template<class F>
auto Async(F&& f) -> std::future<decltype(f())>
{
    typedef decltype(f()) result_type;

    struct Task : tbb::task
    {
        Task(F&& f) : f_(std::forward<F>(f)) {} // is forward correct here?

        virtual tbb::task* execute()
        {
            f_();
            return nullptr;
        }

        std::packaged_task<result_type()> f_;
    };

    auto task = new (tbb::task::allocate_root()) Task(std::forward<F>(f));
    tbb::task::enqueue(*task);
    return task->f_.get_future();
}


int main()
{
    Async([]{ std::cout << "Hi" << std::endl; }).get();
}

      

Live demo.

+3


source to share


2 answers


Is there F&&

still a universal reference at this point? That is, is F

it still considered an inferred type?

This kind of confusion is why I don't like the term universal link ... there is no such thing .

I prefer to understand the code in terms of lvalue references and rvalue references, and the rules for reference collapse and template argument deduction.



When a function is called with an lvalue of type L

, the argument F

will be inferred as L&

, and according to the link F&&

- dropping rules , it's easy L&

. In the constructor, Task

nothing has changed, F&&

still L&

, so the constructor takes a reference lvalue, tied to the lvalue, transmitted to Async

, and so you do not want it to move, and forward

because it keeps the value category, mailing lvalue as an lvalue. (Moving from an lvalue would surprise a caller Async

who wouldn't expect the lvalue to move silently.)

When a function is called with an rvalue of type R

, the argument F

will be inferred as R

, and therefore F&&

is R&&

. In the constructor, Task

nothing has changed, F&&

still R&&

, so the constructor takes rvalue reference tied to the rvalue, passed in Async

, so you can move it, but it forward

is also appropriate because it saves the category values, redirecting both rvalue rvalue.

At CppCon last week, Herb Sutter announced that the preferred term for "universal link" is now referenced links because it better describes what they are used for.

+6


source


The ctor value is not a generic reference, but the standard rvalue-reference or bvalue-reference. The problem with your design is that you don't know what exactly the mirror is Async

(which might be enough)!

To be a universal reference, the type must be inferred for this call, and not sometime before for some associated call.



std::forward

I'm still pertinent there, since the argument to the external functions should indeed be passed to the created object with the preserved move / copy semantics.

+1


source







All Articles