As int() and int{} are constant expressions of value equal to 0, I thought they are equivalent and interchangeable, thus compilers must treat them equally. For example,
int a[0]; //error: zero-sized array not allowed in ISO C++
int b[int()]; //error: zero-sized array not allowed in ISO C++
int c[int{}]; //error: zero-sized array not allowed in ISO C++
But it seems there are some corner cases where they're not interchangeable.
When initializing a pointer:
int *p = 0; //ok
int *q = int(); //error - by clang only
int *r = int{}; //error - by gcc and clang both
See GCC and Clang messages. I suspect this is a bug in both compilers, as I expect them to be interchangeable in this context, but I would be glad to be proven wrong. :-)
When passing to class template:
template<int N> struct X{};
X<0> x1; //ok
X<int{}> x2; //ok (same as X<0>)
X<int()> x3; //error
See GCC and Clang messages.
I find the syntax X<int()> quite familiar as I've seen (and probably used) the similar syntax before, such as in std::function<int()>, the template argument int() is expected to be function type (instead of 0) taking no argument and returning int. But I want to know the section of the spec which says in this context int() is to be treated as function type and is not equivalent to int{} which is always 0.
The expressions int() and int{} are both constant expression prvalues of integer type that evaluate to zero, and are therefore interchangeable with the literal 0 in any context that requires an integral constant expression prvalue of integer type which evaluates to zero.
Both expressions satisfy the requirements for a constant expression as specified in 5.19 Constant Expressions [expr.const].
Regarding X<int()>, the standard specifies that int() is not interpreted as an expression in this context:
14.3 Template arguments [temp.arg]
In a template-argument, an ambiguity between a type-id and an expression is resolved to a type-id, regardless of the form of the corresponding template-parameter.
Regarding pointer conversions:
4.10 Pointer conversions [conv.ptr]
A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero
or a prvalue of type std::nullptr_t.
Based on the above paragraph, both int() and int{} are null pointer constant expressions. This points to a (very minor) compiler bug, although there is an open Defect Report (903) which may lead to this paragraph changing:
There was a strong consensus among the CWG that only the literal 0 should be considered a null pointer constant, not any arbitrary zero-valued constant expression as is currently specified.
The following wording deals with the value of the expression int():
8.5 Initializers [dcl.init]
To zero-initialize an object or reference of type T means:
[omitted clauses which don't apply]
— if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression,
converted to T
[...]
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the
default constructor for T is called (and the initialization is ill-formed if T has no accessible default
constructor);
[omitted clauses which don't apply]
— otherwise, the object is zero-initialized.
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.
And for the value of int{}:
8.5.4 List-initialization [dcl.init.list]
List-initialization of an object or reference of type T is defined as follows:
— If the initializer list has no elements and T is a class type with a default constructor, the object is
value-initialized.
[omitted clauses which don't apply]
— Otherwise, if the initializer list has no elements, the object is value-initialized.
All quotes from C++ Working Draft Standard N3337.
Related
struct S{
constexpr S() {};
};
template <auto x> void f();
int main() {
S s{};
f<s>();
}
First off, per [temp.arg.nontype]/1
If the type T of a template-parameter (13.2) contains a placeholder
type (9.2.9.6) or a placeholder for a deduced class type (9.2.9.7),
the type of the parameter is the type deduced for the variable x in
the invented declaration
T x = template-argument;
If a deduced parameter type is not permitted for a template-parameter
declaration (13.2), the program is ill-formed.
Our template parameter contains a placeholder type auto, so the type of the parameter is the type deduced for the variable x in the invented declaration auto x = s; In this case, the type of the parameter is S, and S is a permitted type for the parameter declaration because S is a structural literal type.
Second, per [temp.arg.nontype]/2
A template-argument for a non-type template-parameter shall be a
converted constant expression (7.7) of the type of the
template-parameter.
This means the template-argument s shall be a converted constant expression. So per [expr.const]/10:
A converted constant expression of type T is an expression, implicitly
converted to type T, where the converted expression is a constant
expression and the implicit conversion sequence contains only
[..]
(10.2) — lvalue-to-rvalue conversions (7.3.2)
[..]
I'm not sure whether or not the lvalue s is converted to prvalue before any implicit conversions are applied to it. Note the definition of converted constant expression in C++14 is relatively changed. N4140 §5.19 [expr.const]/3: (emphasis mine)
A converted constant expression of type T is an expression, implicitly
converted to a prvalue of type T, where the converted expression is a
core constant expression and the implicit conversion sequence contains
only [..]
So per C++14, It's guaranteed that the converted expression is converted to a prvalue before any implicit conversion is applied to it.
I'm not so sure whether or not an lvalue-to-rvalue conversion is applied to template-argument s. In other words, I'm not sure that the lvalue s is converted to a prvalue.
So in general, if I passed an lvalue as a template argument for a non-type template parameter, does an lvalue-to-rvalue conversion applied to that lvalue?
And aside from that, Is the object s constant-initialized?
The meaning of [expr.const]/10 is that whenever an expression appears in a context that requires a "converted constant expression of type T", that expression is "implicitly converted to type T" and the additional restrictions in [expr.const]/10 shall apply.
So s is not "converted to a prvalue before any implicit conversions are applied to it". Rather, s is implicitly converted to type S, and this implicit conversion might involve some standard and/or user-defined conversions, such as an lvalue-to-rvalue conversion. To know whether or not it does, we would have to look at the rules for implicit conversions, which are in [conv.general]. In particular [conv.general]/6 states that
The effect of any implicit conversion is the same as performing the corresponding declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is an lvalue reference type or an rvalue reference to function type ([dcl.ref]), an xvalue if T is an rvalue reference to object type, and a prvalue otherwise.
The expression E is used as a glvalue if and only if the initialization uses it as a glvalue.
I believe the wording here is a relic of older standard editions. What it should say (in modern language) is that when T is a non-reference type, an implicit conversion of E to T yields a prvalue that initializes its result object (call it t) as if by T t = E;.
So we consider S t = s; What does this initialization do? It calls the copy constructor of S to initialize t; [dcl.init.general]/16.6.2.1. There is no lvalue-to-rvalue conversion in this scenario. (Lvalue-to-rvalue conversions on class types are rare, but do occur in some places in the language, e.g., [expr.call]/12, [expr.cond]/7. If an lvalue-to-rvalue conversion were performed on s, it would also call the copy constructor; [conv.lval]/3.2. But in this particular case, the rules of the language do not require an lvalue-to-rvalue conversion.)
Consequently, the result of using s as a template argument for a template parameter of type S is that a prvalue is generated that initializes its result object by calling the copy constructor with s as the argument. (This particular copy constructor is implicitly defined to not do anything, since there are no subobjects to copy.)
This answers your question regarding whether an lvalue-to-rvalue conversion is applied. (You might be wondering what actually happens to the prvalue and whether the copy constructor actually gets called, but that's a separate topic that I don't want to get into right now, because it would make this answer too long.)
As for whether s is constant-initialized, yes it is. It satisfies (2.1) (since it has an initializer) and (2.2) since the full-expression of its initialization is a constant expression (there is nothing to stop it from being a constant expression, since it does nothing other than calling the constexpr default constructor, which itself does nothing). I'm not sure how this is relevant to your other question, though.
int main(){
void{1,2}; // #1
}
#1 is complained be invalid by both GCC and Clang. However, from the point of grammar, it may be valid.
expr.post#general-1
simple-type-specifier braced-init-list
The grammar does not restrict what the braced-init-list should be. From the point of the relevant rule, it also does not have any statement for this case
expr.type.conv#2
If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression. Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of type void that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer. If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.
From the point of definition of the initialization, it states that
dcl.init#general-14
The initialization that occurs
in a functional notation type conversion
Since a prvalue of type void performs no initialization and does not have a result object, hence the whole dcl.init.list#3 is not suitable here
List-initialization of an object or reference of type T is defined as follows:
The case cannot even be caught by dcl.init.list#3.12
Otherwise, the program is ill-formed.
So, I wonder Is any rule states that void{1,2} should be considered ill-formed?
#include <iostream>
int main(){
using type = int[2];
static_cast<type>(type{1,2}); //#1
}
Clang and GCC both complain that #1 is ill-formed, and give weird diagnoses.
Clang reports
static_cast from 'int *' to 'type' (aka 'int [2]') is not allowed
GCC reports
invalid 'static_cast' from type 'type' {aka 'int [2]'} to type 'type' {aka 'int [2]'}
However, as per expr.static.cast#4
An expression E can be explicitly converted to a type T if there is an implicit conversion sequence ([over.best.ics]) from E to T
Isn't that converts a type to the same type be called identity conversion?
over.best.ics#general-8
If no conversions are required to match an argument to a parameter type, the implicit conversion sequence is the standard conversion sequence consisting of the identity conversion ([over.ics.scs]).
I think a crucial rule here is
The sequence of conversions is an implicit conversion as defined in [conv], which means it is governed by the rules for initialization of an object or reference by a single expression ([dcl.init], [dcl.init.ref]).
That means, assume a result object would be t which will be initialized by the prvalue.
type t = type{1,2}; // #2
#2 is also rejected by Clang and GCC. However, such a case should be caught by dcl.init.general#15.9
Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. A standard conversion sequence ([conv]) will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. When initializing a bit-field with a value that it cannot represent, the resulting value of the bit-field is implementation-defined.
According to basic.lval#1.2
A prvalue is an expression whose evaluation initializes an object or computes the value of an operand of an operator, as specified by the context in which it appears, or an expression that has type cv void.
In which case, Isn't that a prvalue of type type cannot initialize the result object?
Clang assumes that array-to-pointer conversion applies to the operand. However, such a conversion is only permitted if the bullet steps into expr.static.cast#8. In other words, the conversion should not apply here for the operand in bullet 4.
GCC gives a more nonreasonable diagnosis. I wonder why the explicit conversion is forbidden by Clang and GCC?
Raw arrays are difficult to keep in array form - in prvalue context an array decays to a pointer.
But does it apply static_cast context also? The rules are outlined in [expr.static.cast]...
We're not casting to a reference, so we skip the first 3 clauses and arrive at [expr.static.cast]/4:
An expression E can be explicitly converted to a type T if there is an implicit conversion sequence ([over.best.ics]) from E to T ...
This won't work because ([conv.general]/3):
An expression E can be implicitly converted to a type T if and only if the declaration
T t=E; is well-formed, for some invented temporary variable t
And int t[2] = int[2]{1, 2}; isn't well-formed. The only legal way to initialize an array is specified in [dcl.init.general]/15.5:
... if the destination type is an array, the object is initialized as follows. Let x1, …, xk be the elements of the expression-list. ... the ith array element is copy-initialized with xi for each 1 ≤ i ≤ k.
There is no provision for "unwrapping" int[2]{1, 2} into an expression-list here.
Otherwise, the result object is direct-initialized from E.
Similarly this won't work for the same reason that int x[2](int[2]{1, 2}); isn't well-formed.
Note that there is a special provision with regard to arrays in aggregates, which allows for easy copying (e.g. std::array). There is no such treatment in raw arrays.
We skip over 5. 6, 7 don't apply. Which brings us to [expr.static.cast]/8:
The lvalue-to-rvalue ([conv.lval]), array-to-pointer ([conv.array]), and function-to-pointer ([conv.func]) conversions are applied to the operand.
. . .
So that's it, the cast is invalid. Both Clang and GCC seem to be correct. The error reporting differs slightly because Clang reports the decayed type, and GCC reports the original type.
As a workaround, cast to a reference type: static_cast<type&&>(type{1,2});.
This is a follow-up of the question: What does the void() in decltype(void()) mean exactly?.
decltype(void()) compiles fine and what the void() means in this case is explained in the above mentioned question (actually in the answer).
On the other side, I noticed that decltype(void{}) doesn't compile.
What's the difference between them (in the context of a decltype at least)?
Why doesn't the second expression compile?
For completeness, it follows a minimal (not-)working example:
int main() {
// this doesn't compile
//decltype(void{}) *ptr = nullptr;
// this compiles fine
decltype(void()) *ptr = nullptr;
(void)ptr;
}
void() is interpreted as type-id when used with sizeof.
void() is interpreted as an expression when used with decltype.
I don't think void{} is valid in any context. It is neither a valid type-id nor a valid expression.
(Building on discussion in the question comments)
Note: I was referencing C++17 or close-to for the answer. C++14 works the same way, the text difference is noted near the end of the answer.
void() is a special exception. See N4618 5.2.3 [expr.type.conv], emphasis mine:
1 A simple-type-specifier (7.1.7.2) or typename-specifier (14.6) followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer. If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction (13.3.1.8) for the remainder of this section.
2 If the initializer is a parenthesized single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). If the type is (possibly cv-qualified) void and the initializer is (), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized (8.6) with the initializer. For an expression of the form T(), T shall not be an array type.
So void() is only valid because it's explicitly identified in [expr.type.conv]/2 as no initialization. void{} doesn't meet that exception, so it attempts to be a direct-initialized object.
tl;dr here through C++14 notes: You cannot direct-initialize a void object.
Rabbit-holing through N4618 8.6 [dcl.init] to see what's actually going on with void{}
8.6/16 => direct-initialization
8.6/17.1 => list-initialized, jump to 8.6.4
8.6.4/3.10 => value-initialized
void() would have shortcut the above three with 8.6/11 => value-initialized, and then rejoined the trail, which is why the special exception in [expr.type.conv] is needed.
8.6/8.4 => zero-initialized
Now, 8.6/6 defines zero-initialize for:
scalar
non-union class type
union type
array type
reference type
N4618 3.9 [basic.types]/9 defines scalar:
Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types(3.9.2), std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types.
N4618 3.9.1 [basic.fundamental]/8 defines arithmetic types:
Integral and floating types are collectively called arithmetic types.
So void is not an arithmetic type, so it's not a scalar, so it cannot be zero-initialized, so it cannot be value-initialized, so it cannot be direct-initialized, so the expression is not valid.
Apart from the initialisation, void() and void{} would work the same way, producing a prvalue expression of type void. Even though you cannot have a result object for an incomplete type, and void is always incomplete:
N4618 3.9.1 [basic.fundamental]/9 (bold mine):
A type cv void is an incomplete type that cannot be completed; such a type has an empty set of values.
decltype specifically allows incomplete types:
N4618 7.1.7.2 [decl.type.simple]/5 (bold mine):
If the operand of a decltype-specifier is a prvalue, the temporary materialization conversion is not applied (4.4) and no result object is provided for the prvalue. The type of the prvalue may be incomplete.
In C++14, N4296 5.2.3 [expr.type.conv] is differently worded. The braced form was almost an afterthought to the parenthesised version:
A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). If the type specified is a class type, the class type shall be complete. If the expression list specifies more than a single value,the type shall be a class with a suitably declared constructor(8.5,12.1), and the expression T(x1, x2, ...) is equivalent in effect to the declaration T t(x1, x2, ...); for some invented temporary variable t, with the result being the value of t as a prvalue.
The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type, whose value is that produced by value-initializing (8.5) an object of type T; no initialization is done for the void() case. [Note: if T is a non-class type that is cv-qualified, the cv-qualifiers are discarded when determining the type of the resulting prvalue (Clause 5). —end note]
Similarly, a simple-type-specifier or typename-specifier followed by a braced-init-list creates a temporary object of the specified type direct-list-initialized (8.5.4) with the specified braced-init-list, and its value is that temporary object as a prvalue.
The effect is the same for our purposes, the change relates to P0135R1 Wording for guaranteed copy elision through simplified value categories, which removed temporary object creation from expressions. Instead, the context of the expression provides a result object to be initialised by the expression, if the context needs it.
As noted above, decltype (unlike sizeof or typeid) doesn't provide a result-object for the expression, which is why void() works even though it cannot initialise a result object.
I feel that the exception in N4618 5.2.3 [expr.type.conv] ought to be applied to void{} too. It means that guidelines around {} get more complex. See for example ES.23: Prefer the {} initializer syntax in the C++ Core Guidelines, which would currently recommend decltype(void{}) over decltype(void()). decltype(T{}) is more likely to be where this one bites you...
The following snippet works fine in Clang 3.5 but not in GCC 4.9.2:
int main()
{
constexpr volatile int i = 5;
}
with error:
error: both 'volatile' and 'constexpr' cannot be used here
If I inspect the assembly that Clang generates, it shows 5 as expected:
movl $5, -4(%rsp)
In GCC, constexpr int i = 5 is optimized away, but volatile int i = 5 also shows 5 in the assembly. volatile const int i = 5 compiles in both compilers. It's not a foreign concept for something to be both volatile and const at the same time.
Which compiler is correct by the standards?
Yes, this is valid, there was defect report 1688: Volatile constexpr variables that was filed for this, saying:
There does not appear to be language in the current wording stating
that constexpr cannot be applied to a variable of volatile-qualified
type. Also, the wording in 5.19 [expr.const] paragraph 2 referring to
“a non-volatile object defined with constexpr” might lead one to infer
that the combination is permitted but that such a variable cannot
appear in a constant expression. What is the intent?
it was rejected as not a defect(NAD), the response and rationale was:
The combination is intentionally permitted and could be used in some
circumstances to force constant initialization.
As the DR points out such a variable is itself not usable in a constant expression:
constexpr volatile int i = 5;
constexpr int y = i ; // Not valid since i is volatile
Section [expr.const]/2 includes all the cases that makes a conditional-expression not a core constant expression including:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
and all the exception require:
[...]that refers to a non-volatile [...] object [...]
Quoting N4140 [dcl.constexpr]/9:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized.
Literal type is defined in [basic.types]/10:
A type is a literal type if it is:
(10.1) — void; or
(10.2) — a scalar type; or
(10.3) — a reference type; or
(10.4) — an array of literal type; or
(10.5) — a class type (Clause 9) that has all of the following properties:
(10.5.1) — it has a trivial destructor,
(10.5.2) — it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
(10.5.3) — all of its non-static data members and base classes are of non-volatile literal types.
Scalar type is in paragraph 9:
Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types.
int is arithmetic, so volatile int is a scalar type and hence a literal type. constexpr volatile int i = 5; is thus a well-formed declaration.
Interestingly, an expression that evaluates i cannot be a core-constant-expression since it applies an lvalue-to-rvalue conversion to a glvalue of volatile type ([expr.const]/2). Consequently, expressions that evaluate i are neither integral constant expressions nor constant expressions. I'm not sure that the constexpr in that declaration has any effect beyond making i implicitly const, and (nod to #T.C.) requiring its initializer to be a constant expression.
I've reported this as GCC bug 65327, we'll see what the GCC folks have to say.
2015-03-16 Update: Bug has been fixed for GCC 5.