Consider the following class that implements a user-conversion to std::string
and const char*
:
class String {
public:
String(std::string_view s) : str{s} {}
operator std::string() const {
return str;
}
operator const char*() const {
return str.c_str();
}
private:
std::string str;
};
int main()
{
static_assert(std::is_convertible_v<String, std::string>);
String s("Hello World");
auto x = static_cast<std::string>(s);
return 0;
}
MSVC tells me that static_casting to std::string
is ambiguous, while clang and gcc do not: https://godbolt.org/z/7de5YvTno
<source>(20): error C2440: 'static_cast': cannot convert from 'String' to 'std::string'
<source>(20): note: No constructor could take the source type, or constructor overload resolution was ambiguous
Compiler returned: 2
Which compiler is right?
As a follow-up question: I can fix the ambiguity by marking the conversion operations explicit. But then std::is_convertible_v<String, std::string>
returns false
. Is there a type trait is_explicitly_convertible
or is_static_castible
or similar?
PS: I know the simplest and cleanest solution would be to have a non-copying conversion operator to const std::string&
, but I still want to understand why MSVC rejects the code.
This is CWG2327.
As written, the direct-initialization specified for static_cast
strictly considers constructors for std::string
, and the String
argument requires incomparable user-defined conversions to satisfy either the move constructor or the converting constructor from const char*
. Note that, regardless of the set of conversion functions available, a constructor must be called, which is the missed copy elision mentioned in the issue.
The intent is that constructors and conversion functions are simultaneously considered for the actual initialization of x
. This is a bit unusual in that member functions of std::string
and String
are in the same overload set, but it resolves the ambiguity because calling operator std::string
is an exact match for the (implied object) argument, and it allows x
to be the return value from that function in the ordinary C++17 sense.
MSVC is implementing the standard as written, while GCC and Clang are implementing (something like) the intended resolution.
(std::is_constructible
is more or less the direct-initialization trait you also asked about.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With