Question

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).
Was it helpful?

Solution

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?

OTHER TIPS

Let's have a closer look at the error message.

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

That means it applies to a copy constructor of std::string

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

Indeed, there's no way to convert from Test to std::string. However, Test has a string member, namely, std::string s;.

Conclusion: it looks like you forgot to add .s at that place. Probably, it is in s(std::forward<T>(t)).

Another possible reason is that the 1st overload of the constructor was picked instead of the 2nd for copy-constructing an instance of Test.

The following should work and it uses no explicit casts:

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)) {} 
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top