【问题标题】:Is this a universal reference? Does std::forward make sense here?这是通用参考吗? std::forward 在这里有意义吗?
【发布时间】:2014-08-21 05:58:14
【问题描述】:

考虑一下这段代码的 sn-p,它使用常见的习惯用法是让函数模板构造一个专门针对推导类型的类模板的实例,如 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) };
}

在斯科特迈耶斯的“普遍参考”的背景下,论点 make_foo 是一个通用引用,因为它的类型是 U&amp;&amp; 其中 U 是 推断。 foo 的构造函数的参数不是通用引用 因为虽然它的类型是T&amp;&amp;,但T(通常)没有被推断出来。

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

所以我的问题是双重的:

  • foo 的构造函数的参数认为是 在推断出T 的有限情况下的通用参考 在调用者的通用引用上下文中,如我的示例所示?
  • 在我的示例中,std::forward 的两种用法都合理吗?

【问题讨论】:

    标签: c++ c++11 language-lawyer perfect-forwarding universal-reference


    【解决方案1】:

    make_foo 与“正确”在同一个球场,但foo 不是。 foo 构造函数目前接受非推导的T &amp;&amp;,并且转发可能不是您的意思(但请参阅@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&lt;int &amp;&gt; 类型的对象。这将在内部存储一个int,但它的构造函数只接受一个int &amp; 参数。相比之下,make_foo(std::move(a)) 将创建一个 foo&lt;int&gt;

    所以按照你写的方式,类模板参数决定了构造函数的签名。 (std::forward&lt;T&gt;(v) 以一种变态的方式仍然有意义(感谢@nodis 指出这一点),但这绝对不是“转发”。)

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

    【讨论】:

    • 或者,在原始构造函数中使用std::move(v)
    • foo&lt;int&amp;&gt; 的构造函数接受左值引用。在这种情况下,使用 std::forward&lt;T&gt; 可能有意义。
    • 在 C++11 的情况下,你需要 return foo&lt;...&gt;(...) 还是不简单的 return { ... } (如问题)工作? (使 C++14 版本的优势最小化)
    • @Oktalist:是的,我对此进行了扩展。你可以这样做,但这将是非常不寻常的。充其量您可以将该构造函数设为私有并成为 maker 函数的朋友,但真正奇怪的是您的 maker 函数创建“不可预测”结果类型的方式。
    • @Oktalist:例如,想象像auto x = make_foo(f()); 这样的代码。后来,有人说,“哦,我可能需要f()”并将其更改为auto const &amp; y = f(); /* ... */ auto x = make_foo(y);。那只是邪恶的:-)所以我的主要观点是,如果制造商函数推导出一个类型(或采用非推导类型参数,如make_shared),它应该衰减类型;但永远不要使用它的原始推导参数类型来确定结果类型。
    【解决方案2】:

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

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

    所以根据这个定义,不,foo 的构造函数中的 T&amp;&amp; v 参数不是通用引用。

    但是,“通用参考”一词的全部意义在于为我们人类在设计、阅读和理解代码时提供一个模型或模式。并且说“当make_foo调用foo&lt;U&gt;的构造函数时,模板参数T已经从make_foo的参数中推导出来,从而使构造函数参数T&amp;&amp; v可以是左值引用,也可以是右值引用(视情况而定)。”这与我可以继续声明的相同概念非常接近:“当make_foo 调用foo&lt;U&gt; 的构造函数时,构造函数参数T&amp;&amp; v 本质上是一个通用引用。”

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

    【讨论】:

      猜你喜欢
      • 2013-06-25
      • 2017-04-10
      • 2010-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-16
      • 1970-01-01
      相关资源
      最近更新 更多