【问题标题】:How to construct an object either from a const reference or temporary via forwarding template如何从 const 引用或通过转发模板临时构造对象
【发布时间】:2019-12-10 05:59:26
【问题描述】:

考虑这个最小的例子

template <class T>
class Foo
{
public:
    Foo(const T& t_)
        : t(t_)
    {
    }

    Foo(T&& t_)
        : t(std::move(t_))
    {
    }

    T t;
};

template <typename F>
Foo<F> makeFoo(F&& f)
{
    return Foo<F>(std::forward<F>(f));
}

int main()
{
    class C
    {

    };

    C c;

    makeFoo(c);
}

MSVC 2017 因 Foo 的 ctor 的重新定义错误而失败。显然,T 被推导出为 C& 而不是预期的 C。这究竟是如何发生的以及如何修改代码以使其执行预期的操作:从 const 引用复制构造 Foo::t 或从 r- 移动构造它价值。

【问题讨论】:

    标签: c++ c++14 perfect-forwarding type-deduction


    【解决方案1】:

    C++17中你可以简单地写:

    template <typename F>
    auto makeFoo(F&& f)
    {
        return Foo(std::forward<F>(f));
    }
    

    因为class template argument deduction


    C++14中你可以这样写:

    template <typename F>
    auto makeFoo(F&& f)
    {
        return Foo<std::decay_t<F>>(std::forward<F>(f));
    }
    

    【讨论】:

      【解决方案2】:
      template <class F, class R = std::decay_t<F>>
      Foo<R> makeFoo(F&& f)
      {
        return Foo<R>(std::forward<F>(f));
      }
      

      这是解决您的问题的一种简洁明了的方法。

      Decay 是将类型转换为适合存储在某处的类型的适当方法。它对数组类型做坏事,但在其他方面做得非常好;您的代码无论如何都不适用于数组类型。


      编译器错误是由于引用折叠规则造成的。

       X          X&          X const&       X&&
       int        int&        int const&     int&&
       int&       int&        int&           int&
       int const  int const&  int const&     int const&&
       int&&      int&        int&           int&&
       int const& int const&  int const&     int const&
      

      这些可能看起来很奇怪。

      第一条规则是 const 引用是一个引用,但是对 const 的引用是不同的。您不能限定“参考”部分;您只能对引用的部分进行 const 限定。

      当你有T=int&amp;,当你计算T constconst T时,你就得到int&amp;

      第二部分与如何一起使用 r 和 l 值引用有关。当您执行int&amp; &amp;&amp;int&amp;&amp; &amp;(您不能直接执行此操作;而是执行T=int&amp; 然后执行T&amp;&amp;T=int&amp;&amp;T&amp;)时,您总是会得到一个左值引用——T&amp;。左值胜过右值。

      然后我们添加如何推导T&amp;&amp; 类型的规则;如果传递C 类型的可变左值,则在对makeFoo 的调用中会得到T=C&amp;

      所以你有:

      template<F = C&>
      Foo<C&> makeFoo( C& && f )
      

      作为你的签名,又名

      template<F = C&>
      Foo<C&> makeFoo( C& f )
      

      现在我们检查Foo&lt;C&amp;&gt;。它有两个演员:

      Foo( C& const& )
      Foo( C& && )
      

      对于第一个,引用上的const 被丢弃:

      Foo( C& & )
      Foo( C& && )
      

      接下来,对引用的引用就是引用,左值引用胜过右值引用:

      Foo( C& )
      Foo( C& )
      

      我们开始了,两个相同的签名构造函数。

      TL;DR - 在这个答案的开头做这件事。

      【讨论】:

      • 为什么它首先会失败? Foo&lt;C&amp;&gt; 应该使两个构造函数分别为Foo(const C&amp;)Foo(C&amp;),它们是可重载的。
      • @0x499602D2 那里,详细描述
      【解决方案3】:

      问题是提供给类的类型名在一种情况下是引用:

      template <typename F>
      Foo<F> makeFoo(F&& f)
      {
          return Foo<F>(std::forward<F>(f));
      }
      

      变成

      template <>
      Foo<C&> makeFoo(C& f)
      {
          return Foo<C&>(std::forward<C&>(f));
      }
      

      你可能想要一些衰变:

      template <typename F>
      Foo<std::decay_t<F>> makeFoo(F&& f)
      {
          return Foo<std::decay_t<F>>(std::forward<F>(f));
      }
      

      【讨论】:

        【解决方案4】:

        这是因为引用崩溃

        代码中的F&amp;&amp;转发引用,这意味着它可以是左值引用右值引用,具体取决于关于它绑定的参数的类型。

        在您的情况下,如果F&amp;&amp; 绑定到C&amp;&amp; 类型的参数(对C 的右值引用),则F 被简单地推导出为C。但是,如果 F&amp;&amp; 绑定到 C&amp; 类型的参数(如您的示例中所示),则引用折叠规则将确定为 F 推导的类型:

        T&  &  -> T&
        T&  && -> T&
        T&& &  -> T&
        T&& && -> T&&
        

        因此,F 推导出为 C&amp;,因为 C&amp; &amp;&amp; 折叠为 C&amp;

        您可以使用remove_reference 从推导的类型中删除任何引用:

        remove_reference_t<C> -> C
        remove_reference_t<C&> -> C
        

        您可能还想使用remove_cv 删除任何潜在的const(或volatile)限定符:

        remove_cv_t<remove_reference_t<C>> -> C
        remove_cv_t<remove_reference_t<C&>> -> C
        remove_cv_t<remove_reference_t<C const>> -> C
        remove_cv_t<remove_reference_t<C const&>> -> C
        

        在 C++20 中,有一个组合的 remove_cvref 特征可以节省一些输入。但是,许多实现只使用decay,它做同样的事情,但也将数组和函数类型转换为指针,这可能会或可能不会根据您的用例而需要(标准库的某些部分已从使用 @ 987654346@ 在 C++20 中使用 remove_cvref)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多