7

Consider the following code:

struct A{
    int x;
    int y;
};
struct B{
    int y;
    int x;
};

void func (A){
}
void func (B){
}

int main()
{
    func({.y=1,.x=1});
}

For some reason both clang and gcc consider this code ambiguous even though it is known that order in designated initializer must match the order in struct, although for some reason clang allows it and just issues a warning:

ISO C++ requires field designators to be specified in declaration order; field 'y' will be initialized after field 'x' [-Wreorder-init-list]

Now the fun begins: If I comment out the func(B) code goes from ambiguous to not compiling.

That is what I consider super weird. Is there some logic behind this?

If the cause of my confusion is not clear:

if we have both func(A) and func(B) in code both gcc and clang consider the func({.y=1,.x=1}) ambiguous, but if I remove func(B) from source then it gives an error(or warning in case of clang). In other words we went from 2 options to 0 options by removing 1 option.

godbolt

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277

1 Answers1

7

The rule is in [over.ics.list]/2:

If the initializer list is a designated-initializer-list, a conversion is only possible if the parameter has an aggregate type that can be initialized from the initializer list according to the rules for aggregate initialization ([dcl.init.aggr]), in which case the implicit conversion sequence is a user-defined conversion sequence whose second standard conversion sequence is an identity conversion.

[Note 1: Aggregate initialization does not require that the members are declared in designation order. If, after overload resolution, the order does not match for the selected overload, the initialization of the parameter will be ill-formed ([dcl.init.list]).

[Example 1:

struct A { int x, y; };
struct B { int y, x; };

void f(A a, int);               // #1
void f(B b, ...);               // #2

void g(A a);                    // #3
void g(B b);                    // #4

void h() {
  f({.x = 1, .y = 2}, 0);       // OK; calls #1
  f({.y = 2, .x = 1}, 0);       // error: selects #1, initialization of a fails
                                // due to non-matching member order ([dcl.init.list])
  g({.x = 1, .y = 2});          // error: ambiguous between #3 and #4
}

end example]

end note]

Basically, the rule that the designators have to name members is in [dcl.init.aggr] (so it counts for whether you can form a conversion sequence) but the rule that designators have to be in order is in [dcl.init.list] (so it's just like a later requirement that happens, and does not affect whether a conversion sequence can be formed - so it's not "sfinae-friendly").

In other words, this is fine:

struct A { int a; };
struct B { int b; };
​
void f(A);
void f(B);
​
void call_f() {
    f({.a=1}); // ok, calls f(A)
}

Because you cannot initialize a B from {.a=1} according to the rules for aggregate initialization.

But your example (and the one quoted above) are ambiguous, since both satisfy the aggregate initialization rules but fail later.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Weird, but I guess that is how standard specifies it... Do you know the motivation? I presume nobody wanted to complicate the language for obscure edge cases? – NoSenseEtAl May 28 '21 at 19:54
  • 1
    @NoSenseEtAl No I don't. – Barry May 28 '21 at 20:14
  • Clang tends to ignore the Standard in this regard on explicit ctors vs list initializations in OR. I guess clang in this case caused the ambiguity (instead of following the spec) because it later accepted the initialization (even though with a warning). – Johannes Schaub - litb May 28 '21 at 22:25
  • 1
    "sfinae-friendly" is the wrong description - this isn't a hard error outside the immediate context, and can be detected by things like `requires`. It's more like deleted conversions, bit-fields, and the like - the check happens after overload resolution, not during it. – T.C. May 29 '21 at 00:05
  • @NoSenseEtAl: The idea is that initializers specified out of order are not “obviously” an attempt to use a different function but might merely be a mistake. Only if the request would be “intrinsically” nonsensical does the language reject the candidate in such a way that might **silently** select an unintended function. – Davis Herring May 29 '21 at 03:10
  • @DavisHerring that makes sense, if you make it an answer you will get upvote from me :) – NoSenseEtAl May 29 '21 at 17:55
  • @T.C. I think Barry knows what sfinae-friendly means, I think he is just using it loosely to explain this complex problem in simpler terms – NoSenseEtAl May 29 '21 at 17:56
  • 1
    @NoSenseEtAl No I think T.C.'s right here. It's still testable, it's just that the differentiation shows up later. – Barry May 29 '21 at 20:25