13

考虑一下这段代码,它使用了一个常见的习惯用法,即让函数模板构造一个专门针对推导类型的类模板的实例,如std::make_uniqueand所示std::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 的“通用引用”的上下文中,参数 to make_foo是通用引用,因为它的类型是推导的U&&位置。U构造函数的参数foo不是通用引用,因为尽管它的类型是T&&,但T(通常)不是推导的。

但是在构造函数的情况下,在foomake_foo看来,将构造函数的参数foo视为通用引用可能是有意义的,因为T已经由函数模板推导出来make_foo。将应用相同的引用折叠规则,以便v两个函数中的类型相同。在这种情况下,两者TU都可以说已经推导出来了。

所以我的问题是双重的:

  • 在调用者在通用引用上下文中推断出来foo的有限情况下,将构造函数的参数视为通用引用是否有意义,例如我的示例?T
  • 在我的例子中,这两种用法都std::forward合理吗?
4

2 回答 2

15

make_foo与“正确”在同一个球场,但foo不是。构造foo函数目前接受一个 non-deduced 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 指出这一点),但这绝对不是“转发”。)

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

于 2014-06-30T19:18:40.257 回答
2

“通用参考”没有正式定义,但我将其定义为:

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

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

然而,“通用参考”这个词的全部意义在于为我们人类在设计、阅读和理解代码时提供一个模型或模式来思考。说“当make_foo调用 的构造函数时foo<U>,模板参数T已经从参数推导出来make_foo,允许构造函数参数T&& v是左值引用或右值引用,这是合理且有帮助的。” 这与我可以继续声明的相同概念非常接近:“当make_foo调用 的构造函数时foo<U>,构造函数参数T&& v本质上是一个通用引用。”

是的,这两种用法都std::forward将按照您的意图进行,如果可能,允许成员v_make_foo参数中移动或复制。但是make_foo(my_str)返回 a foo<std::string&>,而不是 a foo<std::string>,包含 的副本my_str是相当令人惊讶的......

于 2014-06-30T19:56:14.390 回答