Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Supposedly ambiguous explicit conversion operator in MSVC, not in gcc or clang

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.

like image 436
joergbrech Avatar asked Sep 01 '25 10:09

joergbrech


1 Answers

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.)

like image 81
Davis Herring Avatar answered Sep 04 '25 02:09

Davis Herring