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?
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?
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.
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.)