I am trying to understand at a non-superficial level why the following code does not compile:
#include <vector>
template<typename T>
struct wrapper {
T wrapped_value;
wrapper() {}
template<typename... Args>
wrapper(Args&&... args) : wrapped_value( std::forward<Args>(args)... ) {
}
};
struct A {
int a;
A(int i = 0) : a(i) {
}
};
int main() {
std::vector<wrapper<A>> v1;
std::vector<wrapper<A>> v2;
v1 = v2;
}
I can tell from the error message in the std::vector
implementation that the above is failing because the perfect forwarding constructor of wrapper<T>
matches the copy constructor. The copy constructor created by substitution into the constructor template would be
wrapper(wrapper<A>& w) : wrapped_value( w ) {
}
Because wrapped_value
is of type A this is an error since A does not have a constructor accepting a wrapper<A>
.
But isn't "substitution failure not an error"? So the constructor template fails when the compiler attempts to use it as a copy constructor -- why does this block the automatic generation of a copy constructor? Or does it not and the real problem has something to do with the implementation of std::vector
?
Also, this is a toy example but what is the best way around this sort of thing in my real code when dealing with classes like this?
enable_if
et. al.Substitution is not failing and special function generation is not being blocked. Template substitution leads to a constructor that is a better match than the compiler-generated copy constructor so it is selected which causes a syntax error.
Let's simplify the problem illustrated in the question by getting rid of usage of std::vector
. The following will also fail to compile:
#include <utility>
template<typename T>
struct wrapper {
T wrapped_value;
wrapper() {}
template<typename... Args>
wrapper(Args&&... args) : wrapped_value(std::forward<Args>(args)...) {
}
};
struct A {
int a;
A(int i = 0) : a(i) {
}
};
int main() {
wrapper<A> v1;
wrapper<A> v2(v1);
}
In the above template substitution is not failing as applied to the required the copy constructor. We end up with two overloads of the copy constructor, one generated by the compiler as part of special function generation and one produced by substituting the type of v1
into the constructor template:
wrapper(wrapper<A>& rhs); // (1) instantiated from the perfect forwarding template
wrapper(const wrapper<A>& rhs); // (2) compiler-generated ctor.
by the rules of C++ (1) has to be chosen since v1
in the orginal code is not const
. You can actually check this by making it const
and the program will no longer fail to compile.
As for what to do about this, as @jcai mentions in comments, Scott Meyers' Item 27 in Effective Modern C++ is about how to handle this issue -- basically it comes down to either not using perfect forwarding or using "tag dispatch" -- so I will not go into it here.