简体   繁体   中英

Universal references and local classes

In my code below I have a function which accepts "universal reference" ( F&& ). The function also has an inner class which accepts an object of F&& in its constructor. Is F&& still a universal reference at that point? Ie is F still considered to be a deduced type?

In other words, should I use std::forward<F> or std::move in the 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.

Is F&& still a universal reference at that point? Ie is F still considered to be a deduced type?

This kind of confusion is why I dislike the term universal reference ... there's no such thing .

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

When the function is called with an lvalue of type L the argument F will be deduced as L& , and by the reference collapsing rules F&& is just L& . In the Task constructor nothing changes, F&& is still L& so the constructor takes an lvalue reference that is bound to the lvalue passed to Async , and so you don't want to move it, and forward is appropriate because that preserves the value category, forwarding the lvalue as an lvalue. (Moving from the lvalue would surprise the caller of Async , who would not be expecting an lvalue to get silently moved.)

When the function is called with an rvalue of type R the argument F will be deduced as R , and so F&& is R&& . In the Task constructor nothing changes, F&& is still R&& so the constructor takes an rvalue reference that is bound to the rvalue passed to Async , and so you could move it, but forward is also appropriate because that preserves the value category, forwarding the rvalue as an rvalue.

At CppCon last week Herb Sutter announced that the preferred term for a "universal reference" is now forwarding reference because that better describes what they are used for.

The ctor is not a universal reference, but a bog-standard rvalue-reference, or an lvalue-reference. Trouble with your construction is you have no idea which, just that it mirrors Async (which might be enough)!

In order to be a universal-reference, the type would have to be deduced for that call , and not sometime earlier for a somewhat-related call.

std::forward i still appropriate there, as the outer functions argument really should be passed on to the created object with preserved move-/copy-semantics.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM