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

Is this a universal reference? Does std::forward make sense here?

本文关键字:有意义 参考 在这里 std forward 一个      更新时间:2023-10-16

考虑下面这段代码,它使用了一种常用的习惯用法,即让函数模板构造一个类模板的实例,该实例特化于推导出的类型,如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) };
}

在Scott Meyers的"普遍引用"的上下文中,论证make_foo是一个通用引用,因为它的类型是U&&,而U是推导出。foo构造函数的实参不是通用引用因为虽然它的类型是T&&,但T(通常)不是推导出来的。

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

所以我的问题是双重的:

  • 认为foo的构造函数的参数是有意义的吗在有限的情况下推导出T的普遍参考在调用者的通用引用上下文中,如我的示例中?
  • 在我的例子中,std::forward的两个用途是明智的吗?

make_foo与"right"处于相同的大致范围,但foo不是。foo构造函数目前接受非推导的T &&,转发那里可能不是您的意思(但请参阅@nosid的评论)。总而言之,foo应该接受一个类型参数,有一个模板化的构造函数,并且maker函数应该执行分解:

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));
}

在c++ 14中,maker函数的编写变得简单了一些:

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

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

因此,按照您编写它的方式,类模板实参决定了构造函数的签名。(std::forward<T>(v)在某种变态的方式上仍然有意义(感谢@nodis指出这一点),但这绝对不是"转发"。)

那很不寻常。通常,类模板应该确定相关的包装类型,构造函数应该接受任何可以用来创建包装类型的东西,也就是说,构造函数应该是一个函数模板。

"通用引用"没有正式的定义,但我将其定义为:

通用引用是类型为[template-parameter] &&的函数模板的形参,目的是可以从函数实参推导出模板形参,实参将通过左值引用或右值引用适当地传递。

因此,根据这个定义,不,foo的构造函数中的T&& v形参不是一个通用引用。

然而,"通用参考"这个短语的全部意义在于为我们人类在设计、阅读和理解代码时提供一个模型或模式。"当make_foo调用foo<U>的构造函数时,模板形参T已经从make_foo的实参中推导出来,从而允许构造函数形参T&& v作为左值引用或右值引用(视情况而定)。"这与相同的概念非常接近,我可以继续声明:"当make_foo调用foo<U>的构造函数时,构造函数参数T&& v本质上是一个通用引用。"

是的,std::forward的两种用法都可以达到您在这里的目的,允许成员v_make_foo参数中移动,如果可能的话,或者复制。但是让make_foo(my_str)返回foo<std::string&>,而不是foo<std::string>,其中包含my_str的副本是相当令人惊讶的....