Why does direct list initialization causes ambiguity for type reference cast if cast operators to the type and reference to the type are declared?
Asked Answered
O

1

11

The question rose in context of this answer.

Consider an example:

struct foo {
    int value;
    operator int&(){ return value; }
    operator int(){ return value; }
};

int main () {
    int &a(foo{}); // #1
    //int &b{foo{}}; // #2 -- ambiguity
    int &c = foo{}; // #3
    //int &d = {foo{}}; // #4-- ambiguity
    int &d { a }; // #5
    int &e = { a }; // #6
    (void)a;
    (void)c;
    (void)d;
    (void)e;
}

I don't understand why does #2 and #4 cause ambiguity while #1 and #3 does not. So the question is - why does direct list initialization causes ambiguity for implicit cast to reference if cast operators to the type and reference to the type are declared?

Orlan answered 10/12, 2016 at 18:53 Comment(1)
int &a(foo{}) is direct initialization. int &b{foo{}} is direct list initialization. They're not the same thing.Worthless
W
8

List initialization, when used to initialize a reference, will take the listed values and convert them into a prvalue (aka: a temporary), which will be used to direct initialize the reference.

So int &b{foo{}} is functionally equivalent to int &b(int(foo{})). Which is ambiguous; it could generate that int via operator int or operator int&.

But even if it wasn't ambiguous, you would still be getting a non-const lvalue reference to a prvalue. Which is illegal. So this code was never going to work.

Braced-init-lists (curly braces) initialize objects, not references to objects. If you already have an object and want to get a reference to it, don't use braced-init-lists.


But in this case why does compiler accept #5?

Because list initialization is a series of rules with priority. A rule that has a higher priority than the one I pointed out above is the case of a braced-init-list which contains a single value, who's type is identical to the type of what is being initialized. #5 and 6 just so happen to fit that bill, since d, e, and a are all int&s.

But if you just take my advice and not use braced-init-lists when you're not trying to create an object, you won't have to worry about corner-cases like that.

Worthless answered 10/12, 2016 at 19:5 Comment(5)
As not everyone knows about prvalue (for instance, myself). Here is the link to its explanation: https://mcmap.net/q/15524/-what-are-rvalues-lvalues-xvalues-glvalues-and-prvaluesEncratis
But in this case why does compiler accept #5? isn't it similarly equivalent to int &d(int(a));?Orlan
@NicolBolas that answers my question exhaustively! Thanks!Orlan
"Braced-init-lists (curly braces) initialize objects, not references to objects.", that's a bit too much of a simplification. There's no problem with int a = 0; int &b {a}; where it initializes a reference.Philippine
@JohannesSchaub-litb: It's a simplification yes, but a very useful one. It avoids corner-cases like the above.Worthless

© 2022 - 2024 — McMap. All rights reserved.