ISO/IEC JTC1 SC22 WG21, Core Working Group
P0388R4
Robert Haberlach (r.hl{at}gmx{dot}net)
Richard Smith (richard@metafoo.co.uk)
2019-07-18
Permit conversions of arrays of known bound to pointers or references to arrays of unknown bound. This is analogous to evolution issue 118.EWG118
As of core issue 393,CWG393 function parameters can be pointers or references to arrays of unknown bound. However, binding such a parameter to an array of known bound isn't permitted:
void f(int(&)[]);
int arr[1];
f(arr); // Error
int(&r)[] = arr; // Error
This restriction is unjustified and should be removed. One consequence of our approach is the fact that
struct A {
A();
A(const A(&)[2]);
};
using T = A[];
using U = A[2];
A (&&t)[] = {U{}};
changes meaning: at the moment, t binds indirectly to a temporary A{U{}}, while under the new type of reference relation, it binds directly to U{}. This is can be deemed negligible. It was considered during Alberquerque whether binding directly could be the intent when arrays of known but incompatible bound are matched, where the initialiser is enclosed in {}. If so, we could alter the definition of reference-relation to incorporate arrays of all bounds and check that they are compatible at a later stage. However, when the reference is to an array of known bound, the scenario could be along the lines of
namespace json {
struct Value {
Value();
template
Value(const Value(&)[N]);
};
using OneTuple = Value[1];
using TwoTuple = Value[2];
OneTuple const&a = {TwoTuple{}};
}
… which we'd like to preserve as binding indirectly. (Thanks to Richard for the example.)
The initialization of pointers to arrays of unknown bound will be allowed by extending qualification conversions to drop bounds.
Reference initialization rules will be adjusted by modifying reference relation. Such bindings have Exact Match rank.
We also propose to allow list-initialization for references to arrays of unknown bound by deducing the array temporary's size.
Consider
void f(int(&)[]), // (1)
f(int(&)[1]), // (2)
f(int*); // (3)
void h(int(*)[]), // (a)
h(int(*)[1]); // (b)
(2) and (b) should clearly be better than (1) and (a), respectively, as the former overloads are more restricted.
Furthermore, (3) should be equal to (1) (as it is to (2)). To maintain this ambiguity, (1) must be given Exact Match rank. We do not favor this ordering and feel that (3) is conceptually
less specialized than (2) and even (1); however, consistency is more important than fixing part of the problem for arrays of unknown bounds.
Finally, (a) should be worse than (b), which is implied by (a) necessitating a qualification conversion in that case.
We also propose to allow list-initialization and introduce corresponding rules in overload resolution:
int b(int (&&)[] ); // #1
int b(long (&&)[] ); // #2
int b(int (&&)[1]); // #3
int b(long (&&)[1]); // #4
int b(int (&&)[2]); // #5
b({1});
Here,
For these reasons, only if the arrays are of the same type, the size of the referenced array and the known/unknown are primary criterions, (unconditionally) followed by the worst performed conversion.
This is based on N4820.
Hide deleted wordingMove 7.2.2 [expr.type] ¶3 ("The cv-combined type of two types [...]") to the beginning of 7.3.5 [conv.qual] ¶ 3, and modify the resulting 7.3.5 [conv.qual] as follows:
- A cv-decomposition of a type T is a sequence of cvi and Pi such that T is
“cv0 P0 cv1 P1 … cvn-1 Pn-1 cvn U” for n > 0,
where each cvi is a set of cv-qualifiers (6.9.3), and each Pi is “pointer to” (11.3.1), “pointer to member of class Ci of type” (11.3.3), “array of Ni”, or “array of unknown bound of” (11.3.4). If Pi designates an array, the cv-qualifiers cvi+1 on the element type are also taken as the cv-qualifiers cvi of the array. [ Example: The type denoted by the type-id const int ** has two cv-decompositions, taking U as “int” and as “pointer to const int”. — end example ] The n-tuple of cv-qualifiers after the first one in the longest cv-decomposition of T, that is, cv1, cv2, …, cvn, is called the cv-qualification signature of T.- Two types T1 and T2 are similar if they have cv-decompositions with the same n such that corresponding Pi components are either the same or one is “array of Ni” and the other is “array of unknown bound of”, and the types denoted by U are the same.
- The cv-combined type of two types T1 and T2 is
athe type T3 similar to T1 whosecv-qualification signaturecv-decomposition is such that:
[ Note: Given similar types T1 and T2, this construction ensures that both can be converted to T3. — end note ] A prvalue expression of type T1 can be converted to type T2 if the cv-combined type of T1 and T2 is T2.
- for every i > 0,
- cvi3 is the union of cvi1 and cvi2;
- if either Pi1 or Pi2 is “array of unknown bound of”, Pi3 is “array of unknown bound of”, otherwise it is Pi1;
- if the resulting cvi3 is different from cvi1 or cvi2, or the resulting Pi3 is different from Pi1 or Pi2, then const is added to every cvk3 for 0 < k < i.
the following conditions are satisfied, where cvij denotes the cv-qualifiers in the cv-qualification signature of Tj:
(3.1) — T1 and T2 are similar.
(3.2) — For every i > 0, if const is in cvi1 then const is in cvi2, and similarly for volatile.
(3.3) — If the cvi1 and cvi2 are different, then const is in every cvk1 for 0 < k < i.
Drafting note: it may be worth to consider renaming certain terms starting with “cv”, e.g. cv-combined types are not merely combining cv-qualifiers anymore. Possibly an editorial issue?
Modify 7.6.1.10 [expr.const.cast] ¶3:
For two similar types T1 and T2, a prvalue of type T1 may be explicitly converted to the type T2 using a const_cast if, considering the cv-decompositions ([conv.qual]) of both types, all Pi1 are the same as Pi2.
Modify 9.3.4 [dcl.init.list] ¶(3.8) as follows:
Otherwise, if T is a reference type, a prvalueof the type referenced by Tis generated. The prvalue initializes its result object by copy-list-initialization or direct-list-initialization, depending on the kind of initialization for the reference. The prvalue is then used to direct-initialize the reference. The type of the temporary is the type referenced by T, unless T is “reference to array of unknown bound of U”, in which case the type of the temporary is the type of x in the declaration U x[] H, where H is the initializer list.
Modify 12.3.3.1.5 [over.ics.list] ¶6 as follows:
Otherwise, if the parameter type is “array of N X” or “array of unknown bound of X”, if there exists an implicit conversion sequencefor each element of the array from the correspondingfrom each element of the initializer list (orand from {} in the former case ifthere is no such elementN exceeds the number of elements in the initializer list) to X, the implicit conversion sequence is the worst such implicit conversion sequence.
Augment 12.3.3.2 [over.ics.rank] ¶(3.1) as indicated:
List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 ifeven if one of the other rules in this paragraph would otherwise apply. [Example:
- L1 converts to std::initializer_list<X> for some X and L2 does not, or, if not that,
L1 converts to type “array of N1 T”, L2 converts to type “array of N2 T”, and N1 is smaller than N2,- L1 and L2 convert to arrays of the same element type, and either the number of elements n1 initialized by L1 is less than the number of elements n2 initialized by L2, or n1 = n2 and L2 converts to an array of unknown bound and L1 does not,
void f(int (&&)[] ); // #1 void f(double (&&)[] ); // #2 void f(int (&&)[2]); // #3 f( {1} ); // Calls #1: Better than #2 due to conversion, better than #3 due to bounds f( {1.0} ); // Calls #2: Identity conversion is better than floating-integral conversion f( {1.0, 2.0} ); // Calls #2: Identity conversion is better than floating-integral conversion f( {1, 2} ); // Calls #3: Converting to array of known bound is better than to unknown bound, // and an identity conversion is better than floating-integral conversion— end example] [Example:…— end example]
Modify 12.3.3.2 [over.ics.rank] ¶(3.2.5):
S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (7.5), respectively,and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2where T1 can be converted to T2 by a qualification conversion (7.5).
The author would like to thank David Krauss, Johannes Schaub and Richard Smith for their valuable feedback.
[EWG118] “[tiny] Allow conversion from pointer to array of known bound to pointer to array of unknown bound”: wg21.link/ewg118
[CWG393] “Pointer to array of unknown bound in template argument list in parameter”: wg21.link/cwg393
[CWG1307] “Overload resolution based on size of array initializer-list”: wg21.link/cwg1307