类层次结构中完美转发构造函数和复制构造函数之间的冲突

时间:2012-10-31 14:57:56

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

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

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&lt; _Elem,_Traits,_Alloc&gt; :: basic_string(const std :: basic_string&lt; _Elem,_Traits,_Alloc&gt;&amp;)':无法从'const Test'转换参数1到'const std :: basic_string&lt; _Elem,_Traits,_Alloc&gt; &安培;'

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

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

所以我的问题是,如果这个问题还有其他一些解决办法,或者在这种情况下是否有可能完全转发?

更新 事实证明,我的问题很容易被误解。所以我会尝试澄清我的意图和背景。

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

3 个答案:

答案 0 :(得分:2)

尝试将Test(const Test& other) : TestBase(other) {}更改为Test(const Test& other) : TestBase(static_cast<TestBase const&>(other)) {}

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

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

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

答案 1 :(得分:1)

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

std::basic_string<...>::basic_string(const std::basic_string<...> &) :

这意味着它适用于std::string

的复制构造函数

cannot convert parameter 1 from 'const Test' to 'const std::basic_string<..> &

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

结论:您似乎忘了在该地方添加.s。可能是s(std::forward<T>(t))

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

答案 2 :(得分:1)

以下内容应该有效,并且不使用任何显式的强制转换:

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)) {} 
};