【问题标题】:Conflict between perfect forwarding constructor and copy constructor in class hierarchy类层次结构中完美转发构造函数和复制构造函数之间的冲突
【发布时间】:2012-10-31 14:57:56
【问题描述】:

我最近在尝试使用完美的转发构造函数实现类层次结构时遇到了一个问题。 考虑以下示例:

struct TestBase {
  template<typename T>
  explicit TestBase(T&& t) : s(std::forward<T>(t)) {} // Compiler refers to this line in the error message

  TestBase(const TestBase& other) : s(other.s) {}

  std::string s;
};

struct Test : public TestBase {
  template<typename T>
  explicit Test(T&& t) : TestBase(std::forward<T>(t)) {}

  Test(const Test& other) : TestBase(other) {}
};

当我尝试编译代码时,出现以下错误:

错误 3 错误 C2664: 'std::basic_string<_elem>::basic_string(const std::basic_string<_elem> &)' : 无法将参数 1 从 'const Test' 转换为'const std::basic_string<_elem> &'

我的理解是编译器将完美的转发构造函数视为比复制构造函数更好的数学。参见例如Scott Meyers: Copying Constructors in C++11 。在其他没有类层次结构的实现中,我可以通过 SFINAE 禁用完美转发构造函数作为复制构造函数。参见例如Martinho Fernandes: Some pitfalls with forwarding constructors。当我尝试将上述解决方案应用于此示例时,我仍然无法使用相同的错误消息进行编译。

我认为一种可能的解决方案是避免完美转发,在构造函数中按值获取参数,而不是从它们转移到类变量。

所以我的问题是,是否有其他解决方案可以解决这个问题,或者在这种情况下无法完美转发?

更新: 原来我的问题很容易被误解。所以我会试着澄清一下我的意图和背景。

  • 代码是完整的,就像问题中发布的一样。没有创建其他对象或调用函数。尝试编译发布的示例时出现错误。
  • 拥有完美转发构造函数的目的是为了成员初始化,而不是拥有某种额外的复制构造函数。这里的原因是在使用临时对象初始化成员时保存一些对象副本(正如 Scott Meyers 在会谈中提出的那样)
  • 不幸的是,完美的转发构造函数可能与其他重载构造函数(在本示例中为复制构造函数)发生冲突。
  • 就像这个问题的答案和 cmets 建议的那样:这里可能的解决方案是引入显式强制转换或使用单独的非模板构造函数(即,关于具有两个参数分别为 const string&amp;string&amp;&amp; 的构造函数的示例)。

【问题讨论】:

  • 可能是您写了std::string(test) 而不是std::string(test.s) 之类的东西?请告诉我们 main.cc 的第 130 行
  • 你写的是s(std::forward&lt;T&gt;(t))而不是s(std::forward&lt;T&gt;(t).s)
  • 混合重载和转发模板通常是个坏主意。我会从 string 创建一个构造函数并添加一个转发 make_test function 模板。
  • @Andrey 抱歉,我应该突出显示错误消息的来源。 main.cc 中的第 130 行指的是 TestBase 中的完美转发构造函数。我将编辑我的示例...
  • @avakar 参数 t 不应是 Test 类的对象,而是某种字符串(即 std::string 或 cont char*)。我不希望完美的转发构造函数成为复制构造函数,所以我认为我不应该添加.s

标签: c++ c++11 copy-constructor sfinae perfect-forwarding


【解决方案1】:

尝试将Test(const Test&amp; other) : TestBase(other) {} 更改为Test(const Test&amp; other) : TestBase(static_cast&lt;TestBase const&amp;&gt;(other)) {}

第二个Test构造函数调用TestBase,有两种可能。其中一个需要任何东西,另一个需要一个TestBase。但是你正在通过一个测试——“任何东西”匹配得更好。通过显式转换为 TestBase const&,我们应该能够得到正确的匹配。

另一种可能性可能涉及到 Test 是如何构造的——也许你传入的内容与模板构造函数匹配到了 Test ?我们可以通过从 Test 中删除模板构造函数并查看错误是否消失来测试另一种可能性。

如果是这样,为什么您链接的技术(在推断的类型与 Test 匹配时禁用 Test 模板构造函数)不起作用?

【讨论】:

  • 您的解决方案编译时没有错误。应用您的解决方案时是否有任何可能的副作用?通常我喜欢在使用强制转换消除错误/警告时要小心一点。
  • 有问题的演员阵容是安全的。我将 Foo const& 转换为 FooBase const& ——这是您想要隐式执行的操作,但编译器更喜欢您接受所有内容的模板构造函数。我明确表达了你想要隐含发生的事情......
  • 感谢您的解释。还有一个问题:如果我现在像这样向 Test 添加一个移动构造函数:Test(Test&amp;&amp; other) : TestBase(std::move(other)) {}。我再次收到相同的错误消息。对于这种情况,您是否也有解决方案?坦克很多!
  • 您在示例代码中调用了TestBase(Test&amp;&amp;)。你看到哪里了吗?现在,只看TestBase 的定义,它会调用哪个ctor?
  • +1,这是一件非常微妙的事情。我盯着显示器看了一会儿才知道发生了什么。
【解决方案2】:

让我们仔细看看错误信息。

std::basic_string&lt;...&gt;::basic_string(const std::basic_string&lt;...&gt; &amp;) :

这意味着它适用于std::string的复制构造函数

cannot convert parameter 1 from 'const Test' to 'const std::basic_string&lt;..&gt; &amp;

确实,没有办法将Test 转换为std::string。但是,Test 有一个字符串成员,即std::string s;

结论:您好像忘记在那个地方添加.s。应该是s(std::forward&lt;T&gt;(t))

另一个可能的原因是构造函数的第一个重载被选中而不是第二个用于复制构造Test 的实例。

【讨论】:

  • 我想我没有忘记.s。我的想法是参数 t 本身就是我想转发给成员的某种字符串。例如 const char* 或 std::string。
  • @mkh:那么这意味着它以某种方式发生了 T 不是字符串而是Test
  • 我的问题是如何让编译器停止认为 T 可能是一个测试。我不创建任何对象或调用任何函数。
  • @Andrey 是的——看来Test 中的ctor 2 最终“无意”地调用了TestBase 中的ctor 1。然后他们尝试将 Test 转换为字符串,但失败了。
【解决方案3】:

以下应该可以工作,并且它不使用显式强制转换:

struct Test : public TestBase {
  private: 
  static TestBase const& toBase(const Test& o) { return o; }

  public:
  template <typename T>
  explicit Test(T&& t) : TestBase(std::forward<T>(t)) {}

  Test(const Test& other) : TestBase(toBase(other)) {} 
};

【讨论】:

    猜你喜欢
    • 2012-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-13
    • 2013-01-05
    • 1970-01-01
    相关资源
    最近更新 更多