Mordante marked 6 inline comments as done.
Mordante added inline comments.

================
Comment at: clang/test/CXX/drs/dr14xx.cpp:411-414
+  void f(const char[4]);
+  void f(const wchar_t[4]);
+  void f(const char16_t[4]);
+  void f(const char32_t[4]);
----------------
rsmith wrote:
> Mordante wrote:
> > rsmith wrote:
> > > rsmith wrote:
> > > > Mordante wrote:
> > > > > rsmith wrote:
> > > > > > These should presumably be references to arrays, rather than 
> > > > > > arrays, or the parameter type is as if you wrote (for example) 
> > > > > > `void f(const char *)`, which shouldn't get the special treatment 
> > > > > > here.
> > > > > > 
> > > > > > [over.ics.list]p4 mentions this in its footnote:
> > > > > > 
> > > > > > "Otherwise, if the parameter type is a character array [Footnote: 
> > > > > > Since there are no parameters of array type, this will only occur 
> > > > > > as the referenced type of a reference parameter.] and the 
> > > > > > initializer list has a single element that is an 
> > > > > > appropriately-typed string-literal (9.4.3), the implicit conversion 
> > > > > > sequence is the identity conversion."
> > > > > Ah I missed that footnote. I reread the standard and can you confirm 
> > > > > some cases?
> > > > > ```
> > > > > namespace A { 
> > > > >   void f(const char(&)[4]);
> > > > >   void g() { f({"abc"}); }
> > > > > }
> > > > > namespace B { 
> > > > >   void f(const char(&&)[4]);
> > > > >   void g() { f({"abc"}); }
> > > > > } 
> > > > > ```
> > > > > both should work and with P0388 the array without bounds will also 
> > > > > work.
> > > > > 
> > > > > I ask since this is my interpretation of the standard, but it seems 
> > > > > there's a difference between implementations and `void f(const 
> > > > > char(&&)[4]);` fails for "abc" but works for "ab".
> > > > > It seems ICC and consider "abc" an lvalue in this case and not when 
> > > > > using "ab".
> > > > > 
> > > > > Here's a gotbolt link with the examples https://godbolt.org/z/r1TKfx
> > > > That's a really interesting example :)
> > > > 
> > > > The initializer is a list containing an lvalue of type `const char[4]`. 
> > > > Per [dcl.init.list]/3.9 and /3.10, the behavior depends on whether the 
> > > > referenced type is reference-related to `const char[4]` -- if so, then 
> > > > the reference can only bind directly and a `&&` reference will be 
> > > > invalid, because it's binding an rvalue reference to an lvalue, and if 
> > > > not, then we create an array temporary and the `&&` binding is fine.
> > > > 
> > > > Per [dcl.init.ref]/4, `const char[???]` is reference-related to `const 
> > > > char[4]` if they are similar types, and per [conv.qual]/2, the types 
> > > > are similar if `???` is omitted or `4`, and not similar otherwise.
> > > > 
> > > > So:
> > > > * `const char (&&r)[4] = {"abc"}` is ill-formed (types are the same, 
> > > > binds rvalue reference to lvalue)
> > > > * `const char (&&)[] = {"abc"}` is ill-formed (types are similar, binds 
> > > > rvalue reference to lvalue)
> > > > * `const char (&&r)[5] = {"abc"}` is OK (types are not similar, creates 
> > > > temporary)
> > > > 
> > > > That seems like a very surprising outcome to me. Perhaps we should 
> > > > probably ignore array bounds entirely when determining whether two 
> > > > types are reference-related. I'll take this to CWG for discussion.
> > > I think that's only an answer to half of your question. The other half is 
> > > that [over.ics.list]p4 does not apply (directly) to either of your 
> > > testcases, because the "parameter type" is a reference type, not an array 
> > > type. For:
> > > 
> > > ```
> > > namespace A { 
> > >   void f(const char(&)[4]);
> > >   void g() { f({"abc"}); }
> > > }
> > > ```
> > > 
> > > ... we reach [over.ics.list]p9, which says to use the rules in 
> > > [over.ics.ref]. Those rules say that if the reference binds directly to 
> > > an argument expression (ignore the "expression" here; this is very old 
> > > wording that predates braced initializers), then we form an identity 
> > > conversion. So that's what happens in this case.
> > > 
> > > For
> > > 
> > > ```
> > > namespace B { 
> > >   void f(const char(&&)[4]);
> > >   void g() { f({"abc"}); }
> > > }
> > > ```
> > > 
> > > the same thing happens, but now [over.ics.ref]p3 says "an implicit 
> > > conversion sequence cannot be formed if it requires binding an lvalue 
> > > reference other than a reference to a non-volatile const type to an 
> > > rvalue or binding an rvalue reference to an lvalue other than a function 
> > > lvalue", so the candidate is not viable.
> > > 
> > > If the array bound were omitted or were not 4, then the reference would 
> > > not bind directly, and we would instead consider initializing a 
> > > temporary. [over.ics.ref]p2 says "When a parameter of reference type is 
> > > not bound directly to an argument expression, the conversion sequence is 
> > > the one required to convert the argument expression to the referenced 
> > > type according to 12.4.4.2.", which takes us back around to 
> > > [over.ics.list] with the "parameter type" now being the array type. 
> > > That's how we can reach [over.ics.list]p4 and consider string literal -> 
> > > array initialization.
> > > 
> > > So I think that, according to the current rules, for
> > > 
> > > ```
> > > void f(const char (&&)[4]); // #1
> > > void f(const char (&&)[5]); // #2
> > > ```
> > > 
> > > ... a call to `f({"abc"})` remarkably calls #2, because #1 is not viable 
> > > (would bind rvalue reference to lvalue string literal), but #2 is, just 
> > > like `const char (&&r)[4] = {"abc"};` is ill-formed but `const char 
> > > (&&r)[5] = {"abc"};` is valid.
> > > That's a really interesting example :)
> > 
> > Thanks :-)
> > Also thanks for the detailed explanation of the rules applying to these 
> > cases.
> > 
> > > So:
> > > * `const char (&&r)[4] = {"abc"}` is ill-formed (types are the same, 
> > > binds rvalue reference to lvalue)
> > > * `const char (&&)[] = {"abc"}` is ill-formed (types are similar, binds 
> > > rvalue reference to lvalue)
> > 
> > Interesting, wasn't P0388 trying to solve the issues with array of unknown 
> > bound?
> > Am I correct to assume it's intended that this code will become valid in 
> > the future?
> > 
> > > * `const char (&&r)[5] = {"abc"}` is OK (types are not similar, creates 
> > > temporary)
> > > 
> > > That seems like a very surprising outcome to me. Perhaps we should 
> > > probably ignore array bounds entirely when determining whether two types 
> > > are reference-related. I'll take this to CWG for discussion.
> > 
> > I agree this seems unexpected behaviour, thanks for discussing it with CWG.
> > 
> > > So I think that, according to the current rules, for
> > 
> > > void f(const char (&&)[4]); // #1
> > > void f(const char (&&)[5]); // #2
> > 
> > > ... a call to f({"abc"}) remarkably calls #2, because #1 is not viable 
> > > (would bind rvalue reference to lvalue string literal), but #2 is, just 
> > > like const char (&&r)[4] = {"abc"}; is ill-formed but const char (&&r)[5] 
> > > = {"abc"}; is valid.
> > 
> > MSVC agrees with your observation, GCC picks #1 and fails to create the 
> > call, and ICC aborts with an ICE
> > https://godbolt.org/z/s7nTPP
> > 
> > 
> > 
> > Am I correct to assume it's intended that this code will become valid in 
> > the future?
> 
> I think it's more likely that the corresponding cases with other array bounds 
> will become ill-formed. String literals are lvalues, so binding an rvalue 
> reference to a string literal should not be valid based on our normal 
> principles; allowing the wrong-array-bound case is then pretty surprising 
> emergent behavior of the form that we usually disallow (on the basis that it 
> "looks sufficiently like" the intention was for the reference to bind 
> directly, so it's an error if the reference can't bind directly).
> 
> I think the likely outcomes are either that we ignore array bounds when 
> determining reference-relatedness, or that we always create a temporary when 
> binding an rvalue-reference-to-char-array to a string literal, regardless of 
> whether the bound matches. Or maybe that we set the array bound of the string 
> literal to that of the reference-to-array, allowing the `const &` cases but 
> not the `&&` cases with an incorrect bound. But I wouldn't want to guess 
> which.
Thanks for your insight.


================
Comment at: clang/test/SemaCXX/overload-array-size.cpp:6-13
+void f(const char(&&)[4]); // #1
+void f(const char(&&)[5]); // #2
+void g() {
+  f({"abc"});
+  // CHECK: ExprWithCleanups {{.*}} <line:[[@LINE-1]]:{{.*}}> 'void'
+  // CHECK-NEXT: CallExpr
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(const char (&&)[5])'
----------------
rsmith wrote:
> It's more common to test such things via diagnostics rather than by AST 
> dumps. (AST dump tests tend to be more fragile in practice.) You could mark 
> the `[5]` case as `=delete` and check that you get the "call to a deleted 
> function" diagnostic, or you could change the `[5]` case to return `int &` 
> and check that you can initialize an `int &` from a call to it, or something 
> like that.
I'm also not too fond of the AST checks, but forgot I could use `= delete` to 
detect whether the wanted overload is tested. Since I adjusted the other tests 
as suggested this test has no additional value and I removed it.


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D87561/new/

https://reviews.llvm.org/D87561

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to