这是一个普遍的参考?std :: forward在这里有意义吗?

Okt*_*ist 13 c++ language-lawyer perfect-forwarding c++11 universal-reference

考虑这段代码,它使用具有函数模板的常用习惯用法构造专门用于推导类型的类模板的实例,如std::make_uniquestd::make_tuple以下所示:

template <typename T>
struct foo
{
    std::decay_t<T> v_;
    foo(T&& v) : v_(std::forward<T>(v)) {}
};

template <typename U>
foo<U> make_foo(U&& v)
{
    return { std::forward<U>(v) };
}
Run Code Online (Sandbox Code Playgroud)

在斯科特迈尔斯'通用引用’的上下文中,所述参数 make_foo是一个普遍的基准,因为它的类型是U&&其中U推导出.到的构造函数的参数foo不是通用参考,因为尽管其类型为T&&,T是(通常)不能推导出.

但是在foo调用构造函数的情况下,在make_foo我看来,将构造函数的参数foo看作是一个通用引用可能是有意义的,因为T它是由函数模板推导出来的make_foo.将应用相同的参考折叠规则,以便v两个函数中的类型相同.在这种情况下,无论是TU可以说已经推出.

所以我的问题是双重的:

  • foo在有限的情况下,将构造函数的参数视为通用引用是否有意义T,在调用者的通用引用上下文中,如在我的示例中那样?
  • 在我的例子中,两者都是std::forward明智的用途吗?

Ker*_* SB 15

make_foo和"正确"在同一个球场,但foo不是.该foo构造函数目前接受非推断T &&,转发有可能不是你的意思(但看到@ nosid的评论).总而言之,foo应该采用类型参数,具有模板化构造函数,并且制造商函数应该进行衰减:

template <typename T>
struct foo
{
    T v_;

    template <typename U>
    foo(U && u) : v_(std::forward<U>(u)) { }
};

template <typename U>
foo<typename std::decay<U>::type> make_foo(U && u)
{
    return foo<typename std::decay<U>::type>(std::forward<U>(u));
}
Run Code Online (Sandbox Code Playgroud)

在C++ 14中,maker函数变得更简单:

template <typename U>
auto make_foo(U && u)
{ return foo<std::decay_t<U>>(std::forward<U>(u)); }
Run Code Online (Sandbox Code Playgroud)

在您的代码现在编写时,int a; make_foo(a);将创建一个类型的对象foo<int &>.这将在内部存储一个int,但它的构造函数只接受一个int &参数.相比之下,make_foo(std::move(a))会创造一个foo<int>.

所以你编写它的方式,类模板参数决定了构造函数的签名.(std::forward<T>(v)仍然以一种变态的方式有意义(感谢@nodis指出这一点),但这绝对不是"转发".)

这很不寻常.通常,类模板应确定相关的包装类型,构造函数应接受可用于创建包装类型的任何内容,即构造函数应为函数模板.

  • `foo <int&>`的构造函数接受左值引用.在这种情况下使用`std :: forward <T>`可能有意义. (5认同)
  • @Oktalist:例如,想象一下像`auto x = make_foo(f());`这样的代码.后来,有人说,"哦,我可能需要`f()`,"并将其更改为`auto const&y = f();/*...*/auto x = make_foo(y);`.这只是邪恶的:-)所以我的主要观点是,如果一个制造商函数推导出一个类型(或者采用非推导类型的参数,比如`make_shared`),它应该衰减类型; 但从不使用其原始推导参数类型来确定结果类型. (2认同)