As a question that came up during the discussion of this SO question:
Is it legal, maybe with N3471, to declare a constexpr std::initializer_list object? Example:
constexpr std::initializer_list<int> my_list{};
Why I think it may not be legal: initializer_list would have to be a literal type; but are there any guarantees that it is a literal type?
Citations from N3485.
[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 types requirements, [basic.types]/10, sub-bullet class types:
a class type (Clause 9) that has all of the following properties:
it has a trivial destructor,
every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression (5.19),
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
all of its non-static data members and base classes are of non-volatile literal types.
Bonus points ;) for answering if
constexpr std::initializer_list<int> my_list = {1,2,3,4,5};
is legal (with references). Though I think this is covered by the above + [dcl.init.list]/5
Update: The situation got a bit more complicated after the resolution of CWG DR 1684 removed the requirement quoted below. Some more information can be found in this discussion on the std-discussion mailing list and in the related question Why isn't `std::initializer_list` defined as a literal type?
[decl.constexpr]/8:
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1). [...] The class of which that function is a member shall be a literal type (3.9).
Therefore, the changes of N3471 guarantee std::initializer_list will be a literal type.
Note the constexpr ctor alone doesn't require std::initializer_list to be a literal type, see [dcl.constexpr]/4+8. Side note: An object of non-literal type with constexpr ctor can be initialized during constant initialization [basic.start.init]/2] (part of static initialization, performed before any dynamic initialization).
Related
Per my understanding, for class type T to be const-default-constructible type, the default-initialization of T shall invoke a user-provided constructor, or T shall provide a default member initializer for each non-variant non-static data member: ([dcl.init]/7)
A class type T is const-default-constructible if
default-initialization of T would invoke a user-provided constructor
of T (not inherited from a base class) or if
(7.4) each direct
non-variant non-static data member M of T has a default member
initializer [..]
Noting the bold part, it seems to me that const-default-constructible types can only be class types (including unions and structs). Therefore I can't say that const int, for example, is const-default-constructible type since int is not a class type.
You might ask, from where this confusion comes. Basically, [class.default.ctor]/2 says:
A defaulted default constructor for class X is defined as deleted
if:
[..]
(2.4) any non-variant non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer is not
const-default-constructible ([dcl.init]),
[..]
Notice the word "any". This bullet considers cases where X has data member M, and M is of class types as well as non-class types. But per [dcl.init]/7, the const-default-constructible types are limited to only be class types.
Consider the following example,
struct S
{
const int I;
// Is the type of S::I said to be non-const-default-constructible?
S() = default;
};
Is there missing wording? Am I misreading the quotes?
PS: Thanks to #463035818_is_not_a_number for clarifying this. Consider this case:
struct X
{
const int M = 0;
// Is the type of X::M said to be const-default-constructible?
X() = default;
};
Is the type of S::I said to be non-const-default-constructible?
Yes. Like you've quoted in [dcl.init]/7 only class types can be const-default-constructible. The reason for this is non-class types do not have a default constructor, meaning they have no default value that can be used if they are declared like
const T foo;
When you read
any non-variant non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer is not const-default-constructible ([dcl.init])
It is saying that if you have a member in the form of const T name; in your class then the default constructor is deleted if T is not const-default-constructible. In your case that means your constructor is deleted because const int is not const-default-constructible.
In the case of X, M is still not const-default-constructible because it is not a class type. X though is const-default-constructible because M has a brace-or-equal-initializer so [class.default.ctor]/2.4 does not apply.
This can be boiled down into a simple rule: All const objects must have an initializer
Since built in types do not get default initialized they must have a value provided by the programmer.
Non-class types are never const-default-constructible as you have read correctly.
But I don't think there is any wording missing. It seems intentional. The whole point of const-default-constructible is to make sure that you can't accidentally leave an object with an indeterminate value after initialization when you can't change that object's value later.
It would be UB to try to change the value of I after initialization (because it is declared const), but with the defaulted default constructor or without an explicit initializer on I, it would be default-initialized to an indeterminate value (because that is the effect of default-initialization on non-class types) which then is impossible to change. An indeterminate value can be used for almost nothing. The standard doesn't even allow copying it (except for types unsigned char and std::byte).
So there is no reason to allow you to default-construct this S with the defaulted default constructor.
(Technically the standard now allows using placement-new to replace the I object transparently as long as it is not part of a const-complete object, which allows changing its value in some sense, but that shouldn't be a normal use case.)
In the following code (tested locally and on Wandbox):
#include <iostream>
enum Types
{
A, B, C, D
};
void print(std::initializer_list<Types> types)
{
for (auto type : types)
{
std::cout << type << std::endl;
}
}
int main()
{
constexpr auto const group1 = { A, D };
print(group1);
return 0;
}
MSVC 15.8.5 fails to compile with:
error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'
(all referring to the line containing constexpr)
Clang 8 (HEAD) reports:
error: constexpr variable 'group1' must be initialized by a constant expression
constexpr auto const group1 = { A, D };
^ ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
constexpr auto const group1 = { A, D };
^
gcc 9 (HEAD) reports:
In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
18 | constexpr auto const group1 = { A, D };
| ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
19 | print(group1);
| ^~~~~~
| |
| initializer_list<const Types>
Why?
Firstly, they all apparently consider enum-ids to be non-constant, despite them obviously actually being well-known compile-time constant values.
Secondly, MSVC complains about read outside lifetime, but the lifetime of group1 and its values should extend throughout its usage in print.
Thirdly, gcc has a weird const-vs-non-const complaint that I can't make any sense of, since initialiser lists are always const.
Finally, all but gcc will happily compile and run this code without any problems if constexpr is removed. Granted it is not necessary in this case, but I can't see any good reason for it not to work.
Meanwhile gcc will only compile and run the code if the parameter type is changed to std::initializer_list<const Types> -- and making this change causes it to fail to compile in both MSVC and clang.
(Interestingly: gcc 8, with the parameter type change, does successfully compile and run the code including constexpr, where gcc 9 errors out.)
FWIW, changing the declaration to this:
constexpr auto const group1 = std::array<Types, 2>{ A, D };
Does compile and run on all three compilers. So it is probably the initializer_list itself that is misbehaving rather than the enum values. But the syntax is more annoying. (It's slightly less annoying with a suitable make_array implementation, but I still don't see why the original isn't valid.)
constexpr auto const group1 = std::array{ A, D };
Also works, thanks to C++17 template induction. Though now print can't take an initializer_list; it has to be templated on a generic container/iterator concept, which is inconvenient.
When you initialize a std::initializer_list it happens like this:
[dcl.init.list] (emphasis mine)
5 An object of type std::initializer_list is constructed
from an initializer list as if the implementation generated and
materialized a prvalue of type “array of N const E”, where N is the
number of elements in the initializer list. Each element of that array
is copy-initialized with the corresponding element of the initializer
list, and the std::initializer_list object is constructed to
refer to that array. [ Note: A constructor or conversion function
selected for the copy shall be accessible in the context of the
initializer list. — end note ] If a narrowing conversion is required
to initialize any of the elements, the program is ill-formed.
[ Example:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
The initialization will be implemented in a way roughly equivalent to
this:
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
assuming that the implementation can construct an initializer_list
object with a pair of pointers. — end example ]
How that temporary array gets used to initialize the std::initializer_list is what determines if the initializer_list is initialized with a constant expression. Ultimately, according to example (despite being non-normative), that initialization is going to take the address of the array, or its first element, which will produce a value of a pointer type. And that is not a valid constant expression.
[expr.const] (emphasis mine)
5 A constant expression is either a glvalue core constant
expression that refers to an entity that is a permitted result of a
constant expression (as defined below), or a prvalue core constant
expression whose value satisfies the following constraints:
if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a
constant expression,
if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such
an object ([expr.add]), the address of a function, or a null pointer
value, and
if the value is an object of class or array type, each subobject satisfies these constraints for the value.
An entity is a permitted result of a constant expression if it is an
object with static storage duration that is either not a temporary
object or is a temporary object whose value satisfies the above
constraints, or it is a function.
If the array was however a static object, then that initializer would constitute a valid constant expression that can be used to initialize a constexpr object. Since std::initializer_list has an effect of lifetime extension on that temporary by [dcl.init.list]/6, when you declare group1 as a static object, clang and gcc seem allocate the array as a static object too, which makes the initialization's well-formedness subject only to whether or not std::initializer_list is a literal type and the constructor being used is constexpr.
Ultimately, it's all a bit murky.
It appears std::initializer_list does not yet (in C++17) fulfill the requirements of literal type (which is a requirement the type of a constexpr variable has to satisfy).
A discussion on whether it does so in C++14 is found in this post: Why isn't std::initializer_list defined as a literal type?
which itself was a followup to the post discussing Is it legal to declare a constexpr initializer_list object?
I compared the citations provided in the C++14 related post (of the C++14 standard) to the final working draft (of the C++17 standard) and they are the same.
So there is no explicit requirement that std::initializer_list should be a literal type.
Citations from the final working draft of C++17 (n4659):
[basic.types]/10.5
(10.5) a possibly cv-qualified class type (Clause 12) that has all of the
following properties:
(10.5.1) — it has a trivial destructor,
(10.5.2) — it is either a closure type (8.1.5.1), an aggregate type (11.6.1),
or has at least one constexpr constructor or constructor template
(possibly inherited (10.3.3) from a base class) that is not a copy or
move constructor,
(10.5.3) — if it is a union, at least one of its non-static data members is of non-volatile literal type, and
(10.5.4) — if it is not a union, all of its non-static data members and base
classes are of non-volatile literal types.
[initializer_list.syn]/1
An object of type initializer_list provides access to an array of objects of type const E. [ Note:A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 11.6.4. Copying an initializer list does not copy the underlying elements. —end note ]
That is the reason why it is not legal to declare a constexpr initializer_list object.
As a question that came up during the discussion of this SO question:
Is it legal, maybe with N3471, to declare a constexpr std::initializer_list object? Example:
constexpr std::initializer_list<int> my_list{};
Why I think it may not be legal: initializer_list would have to be a literal type; but are there any guarantees that it is a literal type?
Citations from N3485.
[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 types requirements, [basic.types]/10, sub-bullet class types:
a class type (Clause 9) that has all of the following properties:
it has a trivial destructor,
every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression (5.19),
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
all of its non-static data members and base classes are of non-volatile literal types.
Bonus points ;) for answering if
constexpr std::initializer_list<int> my_list = {1,2,3,4,5};
is legal (with references). Though I think this is covered by the above + [dcl.init.list]/5
Update: The situation got a bit more complicated after the resolution of CWG DR 1684 removed the requirement quoted below. Some more information can be found in this discussion on the std-discussion mailing list and in the related question Why isn't `std::initializer_list` defined as a literal type?
[decl.constexpr]/8:
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1). [...] The class of which that function is a member shall be a literal type (3.9).
Therefore, the changes of N3471 guarantee std::initializer_list will be a literal type.
Note the constexpr ctor alone doesn't require std::initializer_list to be a literal type, see [dcl.constexpr]/4+8. Side note: An object of non-literal type with constexpr ctor can be initialized during constant initialization [basic.start.init]/2] (part of static initialization, performed before any dynamic initialization).
I have been looking for this in N4713 for more than two hours to no avail.
I have a C++14 draft which says, at 7.1.6.1 [dcl.type.cv]:
As described in 8.5, the definition of an object or subobject of
const-qualified type must specify an initializer or be subject to
default-initialization
8.5 [dcl.init] says (clause 7):
To default-initialize an object of type T means:
[for a non class, non array type]:
... no initialization is performed
And immediately follows with:
If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type
with a user-provided default constructor.
Therefore a const built-in must have an initializer, since otherwise it would be default-initialized and that is not allowed.
It's worded in quite an indirect way in N4713 [dcl.init] 11.6/7:
If a program calls for the default-initialization of an object of a const-qualified type T, T shall be a const-default-
constructible class type or array thereof.
Not providing an initialiser causes an object to be default-initialised. As such, if it's const qualified and of a built-in type, it violates the "shall" rule above.
Given the following code:
struct f {
};
int main(){
constexpr f f1 ;
//const f f1 ; // This also has the same issue
//constexpr f f1 = {} ; //This works
}
clang and gcc disagree over whether it is valid, with clang providing the following diagnostic (see it live):
error: default initialization of an object of const type 'const f' without a user-provided default constructor
constexpr f f1 ;
^
{}
As far as I can tell f is a literal type and it is initialized by the implicitly defaulted constructor which should allow it to be declared constexpr. Who is correct here?
Note, clang does accept the declaration of f1 if I explicitly add a constexpr default constructor:
constexpr f() {} ;
Does the answer change is f is not an aggregate?
If we start from the draft C++14 standard section 7.1.5 [dcl.constexpr] we can find the requirements for a constexpr object declaration are:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have
literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19).
So is f is a literal type?
Section 3.9 [basic.types] says:
A type is a literal type if it is:
and covers classes in the following bullet:
a class type (Clause 9) that has all of the following properties
it has a trivial destructor,
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
all of its non-static data members and base classes are of non-volatile literal types.
So we are okay on the first and third bullet. To cover the second bullet, we could note that f is an aggregate but if we modify the example slightly, for example if f looked like this:
struct f {
private:
int x = 0 ;
} ;
which would not be an aggregate in either C++11 or C++14 but the issue would still exist. Then we need to show it has a constexpr constructor. Does f have a constexpr constructor?
As far as I can tell section 12.1 [class.ctor] says yes:
[...] If that user-written default constructor would satisfy the requirements of a constexpr constructor (7.1.5),
the implicitly-defined default constructor is constexpr. [...]
But we are unfortunately required to have a user-provided constructor by section 8.5 which says:
If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type
with a user-provided default constructor.
So it looks like clang is correct here and if we look at the following clang bug report: "error: default initialization of an object of const type 'const Z' requires a user-provided default constructor" even when no constructor needed. So clang is basing their lack of support for this due to defect report 253 which does not currently have a proposed wording and it says (emphasis mine):
Paragraph 9 of 8.5 [dcl.init] says:
If no initializer is specified for an object, and the object is of
(possibly cv-qualified) non-POD class type (or array thereof), the
object shall be default-initialized; if the object is of
const-qualified type, the underlying class type shall have a
user-declared default constructor. Otherwise, if no initializer is
specified for an object, the object and its subobjects, if any, have
an indeterminate initial value; if the object or any of its subobjects
are of const-qualified type, the program is ill-formed.
What if a const POD object has no non-static data members? This wording requires an empty initializer for such cases
[...]
Similar comments apply to a non-POD const object, all of whose non-static data members and base class subobjects have default constructors. Why should the class of such an object be required to have a user-declared default constructor?
The defect report is still open but the last comment on it says:
If the implicit default constructor initializes all subobjects, no initializer should be required.
and also notes:
This issue should be brought up again in light of constexpr constructors and non-static data member initializers.
Note the constraints on const qualified types moved around in section 8.5 since the defect report came about. This was due to proposal N2762: Not so Trivial Issues with Trivial which was pre C++11.
Although the defect report is still open, given the constexpr changes and non-static data member initializers it does not seem like a necessary restriction anymore. Especially considering the following requirement on a constexpr constructor:
every non-variant non-static data member and base class sub-object shall be initialized (12.6.2);