Search code examples
c++implicit-conversionexplicitexplicit-conversion

Behavior when both conversion constructor and operator are present and explicitness is involved


I have a piece of code where I have both conversion constructor and conversion operator.

#include <iostream>
struct ClassFloat;

struct ClassInt{
    int value;
    ClassInt(int c) : value(c){std::cout << "From integer\n";};
    ClassInt(ClassFloat x);
    //explicit ClassInt(ClassFloat x);
};

struct ClassFloat{
    float val;
    explicit operator ClassInt() {std::cout << "Conversion operator called\n"; return ClassInt{999};}
    //operator ClassInt() { std::cout << "Conversion operator called\n"; return ClassInt{999};}
};

ClassInt::ClassInt(ClassFloat x){
    std::cout << "Conversion constructor called!\n";
    value = (int)x.val;
}

int main(){
    ClassFloat floatObj{3.5f};
    ClassInt instance1 = floatObj;           // (1)
    ClassInt instance2 = (ClassInt)floatObj; // (2)
    return 1;
}
  1. If both are non-explicit. I get a compiler error saying it is ambiguous for the first expression. Second expression calls the constructor.
  2. If only operator is explicit, both conversions use the constructor.
  3. If only constructor is explicit, second conversion calls the constructor and first one uses the operator.
  4. If both are explicit, I can only compile second expression. It uses the constructor.

I didn't understand why conversion operator wasn't called at the second expression in the second scenario.

I was also expecting an ambiguity error in the fourth scenario (similar to the first scenario) but constructor was picked.

I compiled using g++ 7.4.0 with -pedantic and -std=c++17 flags.


Solution

  • Firstly, c-style cast performes static_cast, then

    (emphasis mine)

    1) If there is an implicit conversion sequence from expression to new_type, or if overload resolution for a direct initialization of an object or reference of type new_type from expression would find at least one viable function, then static_cast<new_type>(expression) returns the imaginary variable Temp initialized as if by new_type Temp(expression);, which may involve implicit conversions, a call to the constructor of new_type or a call to a user-defined conversion operator.

    So given (ClassInt)floatObj; (which is initialized as if ClassInt Temp(floatObj);), the conversion constructor would be always preferred, it would be used directly to construct a ClassInt. While applying the conversion operator requires an implicit conversion from floatObj to ClassInt (then copy-initialize the temporary ClassInt in concept).

    It seems your observed result is a little different with the above summary, for the 1st scenario,

    1. If both are non-explicit. I get a compiler error saying it is ambiguous.

    Only the 1st expression leads to ambiguous issue, the 2nd expression would use the conversion constructor.

    BTW: I tried with gcc 7.3.0, which gives the result as expected.

    To answer your questions,

    I didn't understand why conversion operator wasn't called at the second expression in the second scenario.

    Because conversion constructor is preferred for the 2nd expression (the c-ctyle cast).

    I was also expecting an ambiguity error in the fourth scenario (similar to the first scenario) but constructor was picked.

    Same as above, conversion constructor is preferred for the 2nd expression; the 1st expression would lead to error because both the conversion constructor and conversion operator are marked as explicit.

    Also note that the 1st expression requires implcit conversion, then whether the conversion constructor or the conversion operator are marked as explicit does matter. On the other hand, the 2nd expression is explicit conversion, which doesn't care that.