Here\'s a largely academic exercise in understanding conversion operators, templates and template specializations. The conversion operator template in the following code wo
static_cast
here is equivalent of doing std::string(a)
.
Note that std::string s = std::string(a);
doesn't compile either. My guess is, there are plenty of overloads for the constructor, and the template version can convert a
to many suitable types.
On the other hand, with a fixed list of conversions, only one of those matches exactly a type that the string's constructor accepts.
To test this, add a conversion to const char*
- the non-templated version should start failing at the same place.
(Now the question is why std::string s = a;
works. Subtle differences between that and std::string s = std::string(a);
are only known to gods.)
You can reproduce the problem by just using
std::string t(a);
Combined with the actual error from GCC (error: call of overloaded 'basic_string(MyClass&)' is ambiguous
) we have strong clues as to what may be happening: there is one preferred conversion sequence in the case of copy initialization (std::string s = a;
), and in the case of direct initialization (std::string t(a);
and static_cast
) there are at least two sequences where one of them can't be preferred over the other.
Looking at all the std::basic_string
explicit constructors taking one argument (the only ones that would be considered during direct initialization but not copy initialization), we find explicit basic_string(const Allocator& a = Allocator());
which is in fact the only explicit constructor.
Unfortunately I can't do much beyond that diagnostic: I can't think of a trick to discover is operator std::allocator<char>
is instantiated or not (I tried SFINAE and operator std::allocator<char>() = delete;
, to no success), and I know too little about function template specializations, overload resolution and library requirements to know if the behaviour of GCC is conforming or not.
Since you say the exercise is academic, I will spare you the usual diatribe how non-explicit conversion operators are not a good idea. I think your code is a good enough example as to why anyway :)
I got SFINAE to work. If the operator is declared as:
template <
typename T
, typename Decayed = typename std::decay<T>::type
, typename = typename std::enable_if<
!std::is_same<
const char*
, Decayed
>::value
&& !std::is_same<
std::allocator<char>
, Decayed
>::value
&& !std::is_same<
std::initializer_list<char>
, Decayed
>::value
>::type
>
operator T();
Then there is no ambiguity and the code will compile, the specialization for std::string
will be picked and the resulting program will behave as desired. I still don't have an explanation as to why copy initialization is fine.