Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conflict between perfect forwarding constructor and copy constructor in class hierarchy

I recently encountered a problem while trying to implement a class hierarchy with perfect forwarding constructors. Consider the following example:

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

When I try to compile the code I get the following error:

Error 3 error C2664: 'std::basic_string<_Elem,_Traits,_Alloc>::basic_string(const std::basic_string<_Elem,_Traits,_Alloc> &)' : cannot convert parameter 1 from 'const Test' to 'const std::basic_string<_Elem,_Traits,_Alloc> &'

My understanding is that the compiler treats the perfect forwarding constructor as a better math than the copy constructor. See for example Scott Meyers: Copying Constructors in C++11 . In other implementations without a class hierarchy I could disable the perfect forwarding constructor from being a copy constructor through SFINAE. See for example Martinho Fernandes: Some pitfalls with forwarding constructors. When I try to apply the mentioned solution to this example I still cannot compile with the same error message.

I think one possible solution would be to avoid the perfect forwarding, take the parameters by value in the constructors and than move from them to the class variables.

So my question is if there are some other solutions to this problem or if perfect forwarding in not possible in such a case?

Update: It turned out that my question is easy to misunderstand. So I will try to clarify my intentions and the context a bit.

  • The code is complete like posted in the question. There are no other objects created or functions called. The error appeared while trying to compile the posted example.
  • The purpose of having the perfect forwarding constructor is for member initialization and not to have some kind of extra copy constructor. The reason here is to save some object copies when initializing members with temporary objects (as proposed in talks by Scott Meyers)
  • Unfortunately as it turned out perfect forwarding constructor can conflict with other overloaded constructors (in this example with the copy constructors).
  • Like the answers and comments to this question suggested: Possible solutions here would be to introduce explicit casts or having separate non-templated constructors (i.e. regarding the example having two constructors with parameters const string& and string&& respectively).
like image 552
mkh Avatar asked Oct 31 '12 14:10

mkh


People also ask

Why the argument to a copy constructor is passed by reference?

When we create our own copy constructor, we pass an object by reference and we generally pass it as a const reference. One reason for passing const reference is, we should use const in C++ wherever possible so that objects are not accidentally modified.

Why we Cannot pass the argument by value to a copy constructor?

Passing by value (rather than by reference) means a copy needs to be made. So passing by value into your copy constructor means you need to make a copy before the copy constructor is invoked, but to make a copy you first need to call the copy constructor. Save this answer.

Can a copy constructor have multiple arguments?

A copy constructor has as its first parameter a (possibly const or volatile) reference to its own class type. It can have more arguments, but the rest must have default values associated with them.


1 Answers

Try changing Test(const Test& other) : TestBase(other) {} to Test(const Test& other) : TestBase(static_cast<TestBase const&>(other)) {}

The 2nd Test constructor is calling TestBase, and there are two possibilities. One of them takes anything, the other takes a TestBase. But you are passing a Test to it -- the "anything" matches better. By explicitly casting to a TestBase const&, we should be able to get the right one to match.

Another possibility might involve how Test is constructed -- maybe what you passed in matched the template constructor to Test instead? We can test this other possibility by removing the template constructor from Test and seeing if the error goes away.

If that is the case, why wouldn't the technique you linked (to disable the Test template constructor when the type deduced matches Test) work?

like image 115
Yakk - Adam Nevraumont Avatar answered Sep 29 '22 04:09

Yakk - Adam Nevraumont