【问题标题】:Move semantics from one type to another using templates使用模板将语义从一种类型移动到另一种类型
【发布时间】:2015-07-03 00:34:55
【问题描述】:

是否有可能有一个 struct A 本身带有一个移动构造函数和一些可以从其他类型移动的构造函数(例如 struct B )但是有一个模板推导正在进行,这样类型 B 不是直接硬编码为另一个移动构造函数:

 struct A{
      A()= default;
      A(A&&a){ /* A's move constructor */ }

      template<typename T>
      A(T&&t){ 
        /* (not a move constructor! by std., matches also lvalues) 

        move from t
        (meta programming to check if we can move the type) 
        */
      } 


}

struct B{};

上面的问题是

B b;
A a(std::move(b)); // select the templated constructor (which moves)

A a(b);  // selects the same copy constructor (which moves but we do not want!!

如何做到这一点?

【问题讨论】:

  • 注意T&amp;&amp;t是一个转发引用,它允许T推断引用类型(不像A(B&amp;&amp; b)`)。所以这个构造函数实际上也会匹配左值。
  • 这就是我有如何区分这一点的确切问题,但要继续进行模板推导过程......?
  • 可能有一些技巧,但我想不出任何东西。这将是另一个问题的主题!
  • Argggghhhh 你的问题变了。 FFS。
  • @Gabriel 我强烈建议回滚到原来的问题并单独发布新问题,你所做的只是烦人

标签: c++ templates c++11 c++14 move-semantics


【解决方案1】:

最干净的解决方案是为左值创建一个更好的构造函数,然后=delete它:

struct A{
  A()= default;
  A(A&&a){ /* A's move constructor */ }

  template<class T>
  A(T&&t){ 
  } 
  template<class T>
  A(T const&&)=delete;
  template<class T>
  A(T&t)=delete;
};

在这种方法下,A a(b);会编译失败。

如果您希望左值遵循不同的路径,那么:

struct A{
  A()= default;
  A(A&&a){ /* A's move constructor */ }

  template<class T>
  A(T&&t){ 
    // rvalues end up here
  } 
  template<class T>
  A(T const&&t):A(t) {} // forward to lvalue ctor
  template<class T>
  A(T&t){
    // lvalues end up here
  }
};

使用 SFINAE 可能只值得复杂性如果你不知道你想要这两个选项中的哪一个 - 即,你从其他地方继承构造函数。 p>

最后一个极端情况——A(T const&amp;&amp;)(和A(A const&amp;&amp;))在某些极端情况下可能会出现。我让它表现得像一个左值 ctor,因为你通常不能“从”T const&amp;&amp;“移动”。 (除非T 的胆量是mutable,即使那样我也会认为这是一个坏主意)。

【讨论】:

  • 仅仅删除 T& 构造函数还不够吗?它不是也适用于 const T& 吗?
  • @Yam T const&amp;&amp; 将比 T&amp; 更好地匹配 T&amp;&amp;T const&amp;&amp; 有点奇怪,但例如 int const foo() { return 3; } 有一个 const 右值返回类型。这很奇怪,也很罕见,以至于您可能会忽略去思考它,并且在一个大型、活跃的代码库中多年或更长时间都看不到它的问题,当您确实看到它发生时,它可能是其他一些问题的症状代码呈梨形。但更具体的例子,tuple&lt;const int&gt; 作为右值传递:std::get&lt;0&gt;(std::forward&lt;Tup&gt;(tup)) 将是 int const&amp;&amp; 我认为。
  • @Yakk 由于某些原因,foo() 的类型不是int const,而是int(因为int 是基本类型,参见[expr]/6)。但是您的论点适用于类类型。
  • @Yakk 哎呀,没有看到第二个 & 那里,我以为你正在删除 T& 和 const T&。对不起。
  • @dyp C++ 是一种愚蠢的语言。 :) 我的错,在谈论左值和右值和const 时,我永远不应该使用int,除非我想谈论内置类型所包含的异常。几乎和我创建一个返回数组的函数一样糟糕。 >_> <_>
【解决方案2】:

如果希望构造函数模板接受任意右值,可以使用enable_if

template <
  class T,
  class Sfinae = typename std::enable_if<!std::is_lvalue_reference<T>::value>::type
>
A(T &&t)

这只会在T 没有被推导出为左值引用时启用构造函数,这相当于说参数不是左值。

正如@dyp在cmets中提到的,也可以更明确的表达:

template <
  class T,
  class Sfinae = typename std::enable_if<std::is_rvalue_reference<T&&>::value>::type
>
A(T &&t)

【讨论】:

  • 太棒了,所以通过这种方式,SFINAE 有助于只接受我正在寻找的右值 :-)
  • SFINAE 适用于默认模板参数? typename std::enable_if&lt;!std::is_lvalue_reference&lt;T&gt;::value&gt;::type* = nullptr 似乎是常用的模式。
  • 无论如何,无论哪种情况都会发生编译器错误。但是使用typename std::enable_if&lt;!std::is_lvalue_reference&lt;T&gt;::value&gt;::type* = nullptr生成的错误信息应该更具提示性。
  • @Lingxi 是的,SFINAE 可以很好地用于模板参数。我更喜欢这个,因为对我来说,它比隐藏在函数参数中更明显;但你可以使用任何你更喜欢的。例如,Clang 为两者生成了非常好的消息。如果您真的关心错误消息,您可以添加一个仅接受左值引用的重载,并在其中添加static_assert
  • 不使用否定检查,为什么不测试std::is_rvalue_reference&lt;T&amp;&amp;&gt;::value
【解决方案3】:

只需在构造函数中使用B&amp;&amp;,就像您为A 所做的那样。

【讨论】:

  • 这是另一种解决方案,如果我们想在 A 中硬编码该类型,是的! +1
猜你喜欢
  • 2021-04-11
  • 1970-01-01
  • 1970-01-01
  • 2011-05-30
  • 1970-01-01
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多