c++constructoroverloadingtypecast-operator

Overloading conversion ctor and conversion operator in `To to = from;` and `To to = {from};`


In brief

When I learned about C++ initialization these days, I found To to = from; behaves differently from To to = {from};, where from is of another type From:

If conversion constructor To::To(From &) and conversion operator From::operator To() are provided, both of them will be considered for To to = from;, which will cause a compile error. However, only the conversion constructor will be considered for To to = {from};, which will work fine. What the hack here?

To be detailed

Code to reproduce this case on GCC 10.3 with C++20 applied:

class From;

class To {
    public:
        To() = default;
        To(From &) {std::cout << "conversion constructor\n";}
};
class From {
    public:
        operator To() {std::cout << "conversion operator\n"; return {};}
};

int main(int argc, char **argv) {
    From from;
    // To to1 = from;
    To to2 = {from};
    return 0;
}

Uncomment the line and we'll get the error:

case.cpp: In function ‘int main(int, char**)’:
case.cpp:17:14: error: conversion from ‘From’ to ‘To’ is ambiguous
   17 |     To to1 = from;
      |              ^~~~

From what I can tell, To to = from; is copy initialization. And this page says "the conversion functions and constructors are both considered by overload resolution in copy-initialization", which is pretty much about the error.

What about To to = {from}; then? It is listed as an old way of copy initialization. But it's up untill C++11 and is probably not the case. Is it list initialization? Can someone give an explanation of how it deals with conversion constructor and conversion operator?

Additional discussion

I also experimented with C++11 in-class initialization, with a few more lines of code:

class InClsInitialization {
    private:
        From from;
        // To to1 = from;
        To to2 = {from};
};

The result seems to be the same (i.e., uncomment the line and we'll get the same error), though I don't find which category of initialization it belongs to.


Solution

  • To to = from; is copy-initialization. It falls under [dcl.init.general]/16.6.3, according to which

    ... user-defined conversions that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated ... and the best one is chosen through overload resolution ([over.match]). ...

    Both converting constructors of To and conversion functions of From are considered. (Since this is copy-initialization, explicit functions will be ignored.)

    To to = {from}; is copy-list-initialization. Copy-list-initialization is a type of copy-initialization, but has different rules. Since To is not an aggregate, it falls under [dcl.init.list]/3.7, according to which

    ... constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution ([over.match], [over.match.list]). ...

    Thus, conversion functions are not considered. (Explicit constructors are considered, but the program is ill-formed if one of them is chosen by overload resolution.)