Search code examples
c++implicit-conversioncopy-initialization

Why this copy-initialization (with two implicit conversions) does not work in GCC/Clang?


Please consider the following code:

#include <string.h>

class cstring {
public:
    cstring(const char* str) : begin_(str), end_(str ? str + strlen(str) : 0)
    {
    }

    size_t size() const
    {
        return (size_t)(end_ - begin_);
    }

    const char* data() const
    {
        return begin_;
    }

private:
    const char* begin_;
    const char* end_;
};

class Name {
public:
    Name(cstring str)
    {
    }
};

int main()
{
    cstring str = "Hello World"; // OK

    Name name = str; // OK

    Name name2 = "James"; // error
}

Clang will output an error(GCC has similar output):

Source.cpp:37:10: error: no viable conversion from 'const char [6]' to 'Name'
    Name name2 = "James"; // error
         ^       ~~~~~~~
Source.cpp:24:7: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from
      'const char [6]' to 'const Name &' for 1st argument
class Name {
      ^
Source.cpp:26:5: note: candidate constructor not viable: no known conversion from 'const char [6]' to 'cstring' for 1st
      argument
    Name(cstring str)
    ^
1 error generated.

In the above code, due to the non-explicit constructors, const char* can be implicitly converted to cstring, and cstring can be implicitly converted to Name as well; hence the conversion sequence from const char* to Name exists. Then why Clang/GCC disallow such a copy-initialization (msvc allows this case)?


Solution

  • Gcc and clang are correct, only one user-defined conversion is allowed in implicit conversion sequence.

    Implicit conversion sequence consists of the following, in this order:

    1. zero or one standard conversion sequence;

    2. zero or one user-defined conversion;

    3. zero or one standard conversion sequence.

    You could change copy-initialization to direct-initialization as Name name2 ("James");, in which only one user-defined conversion (from const char* to cstring) is required, and the converted cstring is passed as the argument to the constructor of Name to construct name2.

    In addition, the implicit conversion in copy-initialization must produce T directly from the initializer, while, e.g. direct-initialization expects an implicit conversion from the initializer to an argument of T's constructor.