C++ list initialization allows multiple user-defined conversions
Asked Answered
P

1

7

I was reading this answer, which has the following example:

struct R {};
struct S { S(R); };
struct T { 
    T(const T &); //1 
    T(S);         //2
};
void f(T);
void g(R r) {
    f({r});
}

The answer is related to an old version of [over.best.ics]/4, which back then looked like this:

However, when considering the argument of a constructor or user-defined conversion function that is a candidate by [over.match.ctor] when invoked for the copying/moving of the temporary in the second step of a class copy-initialization, by [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by [over.match.copy], [over.match.conv], or [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are considered.

In the answer it is said that without the highlighted part in the above quote, f({r}) would be ambiguous, because it could use either the first constructor of T (1) or the second constructor (2).

However, as much as I tried, I cannot see how the first constructor (1) is an option. f({r}) results in a copy-list-initialization of T from {r}. If the first constructor is used, the standard allows a conversion from r to the type of the parameter of the constructor. However, just one conversion is not enough, as one would have to go R --> S (using the converting constructor of S) and then S --> T (using the converting constructor of T (2)). And I cannot find anything in the standard that allows more than one user-defined conversion in the cast of list initialization.

I may be missing something. I would appreciate if someone pointed out where I am mistaken, or if I am not, I would like to know what is the purpose of the highlighted section in the quote from the standard.

The current version of the quoted paragraph requires that the only element of the initializer list be an initializer list itself, which would make sense in the example above, if instead of f({r}) there was f({{r}}). In that case, the explanation would be correct.

Thank you.

Papaw answered 31/7, 2018 at 18:34 Comment(0)
A
2

Your observation is correct. The example is well-formed even without the emphasized part, but the reason is a bit different.

And I cannot find anything in the standard that allows more than one user-defined conversion in the cast of list initialization.

In fact, [over.best.ics]/4 is exactly the rule that forbids more than one user-defined conversion. Consider (1) is called, then we must copy-initialize a temporary object of type T with r, which falls into the "or by [over.match.copy], [over.match.conv], or [over.match.ref] in all cases" part, thus user-defined conversions (r -> const T& and r -> S) are forbidden. As a result, we cannot form an implicit conversion sequence for (1), thus (2) wins.

Note the emphasized part was ever deleted due to issue 1758, and came back again with the constraint "the initializer list has exactly one element that is itself an initializer list" due to issue 2076.

Avitzur answered 1/8, 2018 at 13:12 Comment(5)
Thank you for your answer. So what was the reason why the highlighted part was added in the first place? Was it for cases such as: struct S{}; struct T{ T(T&); T(S); }? Because now both constructors of T could be chosen?Papaw
I don't know. I can't find an official document stating the reason.Avitzur
I was referring to the example that I gave in my first comment: struct S{}; struct T{ T(T&); T(S); }. Without the highlighted part, in a call like void f(T); S s; f({s}); both constructors of T would be valid, right?Papaw
No, T& cannot be initialized with s... If you use const T&, there is also no problem because T(S) is an exact match.Avitzur
You are right about the fact that the non-const lvalue reference does not bind to an rvalue, I do not know why I made the parameter a reference in the first place. I will try to find another example that may be the cause of the highlighted part. Thank you.Papaw

© 2022 - 2024 — McMap. All rights reserved.