Home > Net >  Why does C implicit casting work, but explicitly casting throws an error (specific example)?
Why does C implicit casting work, but explicitly casting throws an error (specific example)?

Time:09-12

I was trying to overload the casting operator in C for practice, but I encountered a problem and I can't figure out the issue. In the example, you can implicitly cast fine, but it causes an error when you try to explicitly cast.

struct B
{
    B() = default;
    B( B& rhs ) = default;
};

struct A
{
    operator B()
    {
        return B();
    }
};

int main()
{
    A a;
    B example = a;    //fine
    B example2 = static_cast<B>(a);    //error
}

The error is:

error C2440: 'static_cast': cannot convert from 'A' to 'B'

message : No constructor could take the source type, or constructor overload resolution was ambiguous

The problem only appears if you define the copy constructor in the B structure. The problem goes away, though, if you define the move constructor, too, or make the copy constructor take in a const B& ( B( const B& rhs ) ).

I think the problem is that the explicit cast is ambiguous, but I don't see how.

I was looking at a similar problem, here, but in that case I could easily see how the multiple options for casting led to ambiguity while I can't here.

CodePudding user response:

static_cast<B>(a);

This expression is an rvalue, more loosely described as a temporary value.

The B class has no suitable constructor. The B( B &rhs) constructor is not suitable, mutable lvalue references don't bind to temporaries, hence the compilation failure.

CodePudding user response:

Before C 17:

Both lines do effectively the same thing. B example = /*...*/; is copy-initialization which will first convert the right-hand side to the type B if necessary in some suitable manner, resulting in a temporary object from which example is then initialized by choosing a suitable constructor (typically a copy or move constructor).

Because A is not related to B via inheritance, there is no way to bind a directly to the rhs parameter in the (copy) constructor of B. There must first be a conversion from a to a B which is possible implicitly or explicitly via the conversion function you defined.

The result of the conversion will always be a prvalue. A prvalue however can not be bound to a non-const lvalue reference. Therefore it doesn't help that the conversion is possible, the B( B& rhs ) constructor overload is still not viable.

A constructor B( const B& rhs ) (which is also the signature of the implicitly-declared copy constructor if you don't declare one yourself) would however be viable because the prvalue resulting from the conversion can be bound to a const lvalue reference.

Therefore both lines are ill-formed, before C 17.


Since C 17 the mandatory copy elision rules state that initialization of a variable of a type B from a prvalue of type B is done exactly as if the variable was directly initialized by the initializer of the prvalue, so that no overload resolution on the constructor will be performed at all. Whether there is a copy constructor or how exactly it is declared is then irrelevant. Similarly the rules for copy-initialization from a non-reference compatible lvalue such as a have been updated to have the same effect.

So since C 17 both lines will compile.


If you are using MSVC in a default configuration with the language standard set below C 17, the first line will compile, but that is not standard-conforming. By default when set to language versions below C 20 MSVC uses a slight dialect of C which is not conforming to the standard in some areas. You can set the standards conformance mode (/permissive- flag) to set MSVC to behave (more) standard-conforming.

  • Related