Conflict between the ideal forwarding constructor and the copy constructor in the class hierarchy

I recently ran into a problem while trying to implement a class hierarchy with advanced 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 of 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> & '

I understand that the compiler sees the ideal forwarding constructor as 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 ideal transfer constructor from the copy constructor via SFINAE. See For example Martinho Fernandes: some pitfalls with call forwarding constructors . When I try to apply the indicated solution to this example, I still cannot compile with the same error message.

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

So my question is, are there any other solutions to this problem, or if perfect forwarding is not possible in that case?

Update: It turned out that my question is easily misunderstood. Therefore, I will try to clarify a little my intentions and context.

  • The code is complete as posted in the question. There are no other objects created or called functions. An error occurred while trying to compile the published example.
  • The goal of creating the ideal forwarding constructor is to initialize the member and not have any additional copy constructor. The reason here is to save some copies of objects when initializing members with temporary objects (as suggested by Scott Meyers in conversations).
  • Unfortunately, as it turned out, the ideal redirect constructor may conflict with other overloaded constructors (in this example, copy constructors).
  • As well as answers and comments to this question: Possible solutions here would be to introduce explicit casts or have separate constructors without templates (i.e., relative to an example that has two constructors with const string& and string&& parameters, respectively).
+7
source share
3 answers

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

The second Test constructor calls TestBase, and there are two possibilities. One of them takes anything, the other takes TestBase. But you pass the test to it - "everything" is better suited. By casting it explicitly to TestBase const &, we should be able to get the correct answer.

Another possibility might be how the test is designed - maybe you passed in according to the template constructor instead of Test? We can test this other possibility by removing the template constructor from Test and seeing that the error has disappeared.

If so, why not use the technique you linked (to disable the test pattern constructor when the type deduced by the Test match)?

+2
source

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

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

This means that it applies to the copy constructor std::string

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

In fact, there is 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 in this place. It is probably in s(std::forward<T>(t)) .

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

+1
source

The following should work and does not use 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)) {} }; 
+1
source

All Articles