7

I've browsed through a lot of questions related to conversion, but it seemed none of them discussing about explicit keyword in this way. Here's the code:

struct B;
struct A{
    /*explicit*/ A(const B&){ cout << 1; }  // *1
};
struct B{
    /*explicit*/ operator A()const{ cout << 2; } // *2
};

void foo(const A &){}

int main(void){
    B b;
    foo( /*static_cast<A>*/ (b) ); // *3
}

Result: (Y: uncommented, N:commented, X: either)

# | *1 | *2 | *3 |output|  
1 |  N |  N |  N |error |
2 |  N |  N |  Y |  1   |
3 |  N |  Y |  N |  1   |
4 |  N |  Y |  Y |  1   |
5 |  Y |  N |  N |  2   |
6 |  Y |  N |  Y |  1   |
7 |  Y |  Y |  N |error |
8 |  Y |  Y |  Y |  1   |

1, 7 are errors, which is normal.(ambiguous and no auto conversion)
2 seems that constructor have higher precedence, but why?
3, 5 are easy to understand.
4 is weird since it doesn't call the explicit one. why?
6 may be due to 'explicit' or constructor having higher precedence. Which one is the reason? 8 seems that constructor have higher precedence, but why?

Could someone offer some explanations? Thanks!

Asu
  • 75
  • 4
  • This is one reason for the rule-of-thumb that you should not have both a converting constructor and a conversion operator - you hardly ever get it right. And when you do, very few people understand why. – Bo Persson Dec 31 '15 at 10:50
  • @BoPersson , I do know that. I'm just curious about why the compiler acts like this. It's not a practical question, I know. Thanks – Asu Dec 31 '15 at 10:54

1 Answers1

2

A very good question.

First of all, the explicit thing doesn't mean "this has precedence if explicit conversion is required". It means "this thing can only be invoked explicitly". So it creates some situations where it cannot be invoked, not forces it to be invoked in other situations.

Another thing to consider is that static_cast is direct initialization, while passing an argument to a function is copy initialization. Among other things, copy initialization never uses explicit constructors. Another thing to note is that direct initialization requires use of constructors for classes (explicit or not). Although that doesn't mean that a conversion can't perform direct initialization: it can be used to convert the argument of a constructor and if the constructor is a compiler-generated copy one, then it would look like the conversion function performed direct initialization (whereas in fact it was performed by the copy constructor). Try declaring a copy constructor without defining it (the disable-copy technique) and you'll see that conversion function no longer works in the direct initialization context: it'll compile, but will cause a linking error.

With that in mind:

  1. Obvious.
  2. Direct initialization requires a constructor, so it is invoked.
  3. Obvious.
  4. The same as 2, really. Declaring the conversion function explicit only prevents it from being invoked implicitly, it doesn't force its use in explicit contexts.
  5. Obvious.
  6. Again, direct initialization requires a constructor and it allows use of explicit ones.
  7. Obvious.
  8. Another direct initialization case.
Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
  • I changed the foo function to void foo(A){} and add A(const A &) = delete; in the struct A. Perform it in case #5 and yes, it's error now. Thanks! – Asu Jan 01 '16 at 03:05
  • Moreover, it seems like this so called "precedence" is actually an overloading process. A conversion constructor is a exact match, while a operator conversion is in fact using copy constructor with a parameter conversion, which is not a exact match. Hope that I didn't misunderstand it. – Asu Jan 01 '16 at 03:11
  • @Asu, to be more precise, it's called overload _resolution_ process, but yes, that's the general idea. And I don't think you need to change the `foo` signature. Reference or not, since the type of the actual parameter is different from the type of the formal parameter, the compiler still needs to create a copy anyway, either to pass it itself or a reference to it. – Sergei Tachenov Jan 01 '16 at 04:30