I am trying to understand why the compiler is complaining here:
// cexpr_test.cpp
#include <initializer_list>
constexpr int test_cexpr(std::initializer_list<const char*> x)
{
return (int) (*x.begin())[0]; // ensuring the value isn't optimized out.
}
int main()
{
constexpr int r1 = test_cexpr({ "why does this work," });
constexpr std::initializer_list<const char*> broken { "but this doesn't?" };
constexpr int r2 = test_cexpr(broken);
return r1 + r2;
}
The message produced when compiled with
g++ -std=c++11 -Wall -Werror cexpr_test.cpp
is as follows:
cexpr_test.cpp: In function ‘int main()’:
cexpr_test.cpp:12:76: error: ‘const std::initializer_list{((const char* const*)(&)), 1}’ is not a constant expression
12 | constexpr std::initializer_list broken { "but this doesn't?" };
|
It's confusing why it constructs the first initializer list without any issues. What am I missing here?
The problem is with the initialization of broken itself here. What is a std::initializer_list and what does it hold? It's a reference type (i.e. refers somehow to another objects), and it's backed by a c-style array. The properties of this c-style array are what determines if the initializer_list can be a constexpr variable. We can consult [dcl.init.list] for those properties.
5 An object of type std::initializer_list<E> 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<E> 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 ]
6 The array has the same lifetime as any other temporary object,
except that initializing an initializer_list object from the array
extends the lifetime of the array exactly like binding a reference to
a temporary. [ Example:
typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}
struct A {
std::initializer_list<int> i4;
A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference
};
For v1 and v2, the initializer_list object is a parameter in a
function call, so the array created for { 1, 2, 3 } has
full-expression lifetime. For i3, the initializer_list object is
a variable, so the array persists for the lifetime of the variable.
For i4, the initializer_list object is initialized in the
constructor's ctor-initializer as if by binding a temporary array to a
reference member, so the program is ill-formed ([class.base.init]).
— end example ] [ Note: The implementation is free to allocate the
array in read-only memory if an explicit array with the same
initializer could be so allocated. — end note ]
So this array is like any other temporary object that is referred to by a constant reference. Which means we can actually reduce your minimal example to something even smaller
constexpr int test_cexpr(int const & x)
{
return x;
}
int main()
{
constexpr int r1 = test_cexpr(0);
constexpr int const &broken = 0;
constexpr int r2 = test_cexpr(broken);
return r1 + r2;
}
This produces the exact same behavior and error. We can pass 0 directly as an argument to the constexpr function, the reference binds, and we can even refer to it inside the function. However, a constexpr reference may not be initialized with 0. The reason for zero not being a valid initializer, is that it requires materializing a temporary int object. This temporary is not a static variable, and so may not be used to initialize a constexpr reference. Simple as that.
The same reasoning applies to your case. The temporary array that's materialized is not an object with static storage duration, so it may not be used to initialize a constexpr reference type.
The reason it works when directly passing the argument to test_cexpr, is that the corresponding parameter is not itself a constexpr variable. Which means it can bind successfully. After which, the thing it's bound to just has to be usable in a constant expression. Without going into too much detail over this: since the temporary in that case has full-expression lifetime (and not lifetime extended), it is usable in a constant expression.
Related
GCC's implementation destroys a std::initializer_list array returned from a function at the end of the return full-expression. Is this correct?
Both test cases in this program show the destructors executing before the value can be used:
#include <initializer_list>
#include <iostream>
struct noisydt {
~noisydt() { std::cout << "destroyed\n"; }
};
void receive( std::initializer_list< noisydt > il ) {
std::cout << "received\n";
}
std::initializer_list< noisydt > send() {
return { {}, {}, {} };
}
int main() {
receive( send() );
std::initializer_list< noisydt > && il = send();
receive( il );
}
I think the program should work. But the underlying standardese is a bit convoluted.
The return statement initializes a return value object as if it were declared
std::initializer_list< noisydt > ret = { {},{},{} };
This initializes one temporary initializer_list and its underlying array storage from the given series of initializers, then initializes another initializer_list from the first one. What is the array's lifetime? "The lifetime of the array is the same as that of the initializer_list object." But there are two of those; which one is ambiguous. The example in 8.5.4/6, if it works as advertised, should resolve the ambiguity that the array has the lifetime of the copied-to object. Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.
On LWS, GCC erroneously kills the array before returning, but it preserves a named initializer_list per the example. Clang also processes the example correctly, but objects in the list are never destroyed; this would cause a memory leak. ICC doesn't support initializer_list at all.
Is my analysis correct?
C++11 §6.6.3/2:
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
8.5.4/1:
… list-initialization in a copy-initialization context is called copy-list-initialization.
8.5/14:
The initialization that occurs in the form T x = a; … is called copy-initialization.
Back to 8.5.4/3:
List-initialization of an object or reference of type T is defined as follows: …
— Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).
8.5.4/5:
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an array of N elements of type 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<E> object is constructed to refer to that array. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.
8.5.4/6:
The lifetime of the array is the same as that of the initializer_list object. [Example:
typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}
For v1 and v2, the initializer_list object and array createdfor { 1, 2, 3 } have full-expression lifetime. For i3, the initializer_list object and array have automatic lifetime. — end example]
A little clarification about returning a braced-init-list
When you return a bare list enclosed in braces,
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
This doesn't imply that the object returned to the calling scope is copied from something. For example, this is valid:
struct nocopy {
nocopy( int );
nocopy( nocopy const & ) = delete;
nocopy( nocopy && ) = delete;
};
nocopy f() {
return { 3 };
}
this is not:
nocopy f() {
return nocopy{ 3 };
}
Copy-list-initialization simply means the equivalent of the syntax nocopy X = { 3 } is used to initialize the object representing the return value. This doesn't invoke a copy, and it happens to be identical to the 8.5.4/6 example of an array's lifetime being extended.
And Clang and GCC do agree on this point.
Other notes
A review of N2640 doesn't turn up any mention of this corner case. There has been extensive discussion about the individual features combined here, but I don't see anything about their interaction.
Implementing this gets hairy as it comes down to returning an optional, variable-length array by value. Because the std::initializer_list doesn't own its contents, the function has to also return something else which does. When passing to a function, this is simply a local, fixed-size array. But in the other direction, the VLA needs to be returned on the stack, along with the std::initializer_list's pointers. Then the caller needs to be told whether to dispose of the sequence (whether they're on the stack or not).
The issue is very easy to stumble upon by returning a braced-init-list from a lambda function, as a "natural" way to return a few temporary objects without caring how they're contained.
auto && il = []() -> std::initializer_list< noisydt >
{ return { noisydt{}, noisydt{} }; }();
Indeed, this is similar to how I arrived here. But, it would be an error to leave out the -> trailing-return-type because lambda return type deduction only occurs when an expression is returned, and a braced-init-list is not an expression.
std::initializer_list is not a container, don't use it to pass values around and expect them to persist
DR 1290 changed the wording, you should also be aware of 1565 and 1599 which aren't ready yet.
Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.
No, that doesn't follow. The array's lifetime doesn't keep being extended along with the initializer_list. Consider:
struct A {
const int& ref;
A(const int& i = 0) : ref(i) { }
};
The reference i binds to the temporary int, and then the reference ref binds to it as well, but that doesn't extend the lifetime of i, it still goes out of scope at the end of the constructor, leaving a dangling reference. You don't extend the underlying temporary's lifetime by binding another reference to it.
Your code might be safer if 1565 is approved and you make il a copy not a reference, but that issue is still open and doesn't even have proposed wording, let alone implementation experience.
Even if your example is meant to work, the wording regarding lifetime of the underlying array is obviously still being improved and it will take a while for compilers to implement whatever final semantics are settled on.
The wording you refer to in 8.5.4/6 is defective, and was corrected (somewhat) by DR1290. Instead of saying:
The lifetime of the array is the same as that of the initializer_list object.
... the amended standard now says:
The array has the same lifetime as any other temporary object (12.2 [class.temporary]), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary.
Therefore the controlling wording for the lifetime of the temporary array is 12.2/5, which says:
The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement
Therefore the noisydt objects are destroyed before the function returns.
Until recently, Clang had a bug that caused it to fail to destroy the underlying array for an initializer_list object in some circumstances. I've fixed that for Clang 3.4; the output for your test case from Clang trunk is:
destroyed
destroyed
destroyed
received
destroyed
destroyed
destroyed
received
... which is correct, per DR1290.
GCC's implementation destroys a std::initializer_list array returned from a function at the end of the return full-expression. Is this correct?
Both test cases in this program show the destructors executing before the value can be used:
#include <initializer_list>
#include <iostream>
struct noisydt {
~noisydt() { std::cout << "destroyed\n"; }
};
void receive( std::initializer_list< noisydt > il ) {
std::cout << "received\n";
}
std::initializer_list< noisydt > send() {
return { {}, {}, {} };
}
int main() {
receive( send() );
std::initializer_list< noisydt > && il = send();
receive( il );
}
I think the program should work. But the underlying standardese is a bit convoluted.
The return statement initializes a return value object as if it were declared
std::initializer_list< noisydt > ret = { {},{},{} };
This initializes one temporary initializer_list and its underlying array storage from the given series of initializers, then initializes another initializer_list from the first one. What is the array's lifetime? "The lifetime of the array is the same as that of the initializer_list object." But there are two of those; which one is ambiguous. The example in 8.5.4/6, if it works as advertised, should resolve the ambiguity that the array has the lifetime of the copied-to object. Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.
On LWS, GCC erroneously kills the array before returning, but it preserves a named initializer_list per the example. Clang also processes the example correctly, but objects in the list are never destroyed; this would cause a memory leak. ICC doesn't support initializer_list at all.
Is my analysis correct?
C++11 §6.6.3/2:
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
8.5.4/1:
… list-initialization in a copy-initialization context is called copy-list-initialization.
8.5/14:
The initialization that occurs in the form T x = a; … is called copy-initialization.
Back to 8.5.4/3:
List-initialization of an object or reference of type T is defined as follows: …
— Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).
8.5.4/5:
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an array of N elements of type 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<E> object is constructed to refer to that array. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.
8.5.4/6:
The lifetime of the array is the same as that of the initializer_list object. [Example:
typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}
For v1 and v2, the initializer_list object and array createdfor { 1, 2, 3 } have full-expression lifetime. For i3, the initializer_list object and array have automatic lifetime. — end example]
A little clarification about returning a braced-init-list
When you return a bare list enclosed in braces,
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
This doesn't imply that the object returned to the calling scope is copied from something. For example, this is valid:
struct nocopy {
nocopy( int );
nocopy( nocopy const & ) = delete;
nocopy( nocopy && ) = delete;
};
nocopy f() {
return { 3 };
}
this is not:
nocopy f() {
return nocopy{ 3 };
}
Copy-list-initialization simply means the equivalent of the syntax nocopy X = { 3 } is used to initialize the object representing the return value. This doesn't invoke a copy, and it happens to be identical to the 8.5.4/6 example of an array's lifetime being extended.
And Clang and GCC do agree on this point.
Other notes
A review of N2640 doesn't turn up any mention of this corner case. There has been extensive discussion about the individual features combined here, but I don't see anything about their interaction.
Implementing this gets hairy as it comes down to returning an optional, variable-length array by value. Because the std::initializer_list doesn't own its contents, the function has to also return something else which does. When passing to a function, this is simply a local, fixed-size array. But in the other direction, the VLA needs to be returned on the stack, along with the std::initializer_list's pointers. Then the caller needs to be told whether to dispose of the sequence (whether they're on the stack or not).
The issue is very easy to stumble upon by returning a braced-init-list from a lambda function, as a "natural" way to return a few temporary objects without caring how they're contained.
auto && il = []() -> std::initializer_list< noisydt >
{ return { noisydt{}, noisydt{} }; }();
Indeed, this is similar to how I arrived here. But, it would be an error to leave out the -> trailing-return-type because lambda return type deduction only occurs when an expression is returned, and a braced-init-list is not an expression.
std::initializer_list is not a container, don't use it to pass values around and expect them to persist
DR 1290 changed the wording, you should also be aware of 1565 and 1599 which aren't ready yet.
Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.
No, that doesn't follow. The array's lifetime doesn't keep being extended along with the initializer_list. Consider:
struct A {
const int& ref;
A(const int& i = 0) : ref(i) { }
};
The reference i binds to the temporary int, and then the reference ref binds to it as well, but that doesn't extend the lifetime of i, it still goes out of scope at the end of the constructor, leaving a dangling reference. You don't extend the underlying temporary's lifetime by binding another reference to it.
Your code might be safer if 1565 is approved and you make il a copy not a reference, but that issue is still open and doesn't even have proposed wording, let alone implementation experience.
Even if your example is meant to work, the wording regarding lifetime of the underlying array is obviously still being improved and it will take a while for compilers to implement whatever final semantics are settled on.
The wording you refer to in 8.5.4/6 is defective, and was corrected (somewhat) by DR1290. Instead of saying:
The lifetime of the array is the same as that of the initializer_list object.
... the amended standard now says:
The array has the same lifetime as any other temporary object (12.2 [class.temporary]), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary.
Therefore the controlling wording for the lifetime of the temporary array is 12.2/5, which says:
The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement
Therefore the noisydt objects are destroyed before the function returns.
Until recently, Clang had a bug that caused it to fail to destroy the underlying array for an initializer_list object in some circumstances. I've fixed that for Clang 3.4; the output for your test case from Clang trunk is:
destroyed
destroyed
destroyed
received
destroyed
destroyed
destroyed
received
... which is correct, per DR1290.
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.
A scalar type is defined as
Trait class that identifies whether T is a scalar type. A scalar type
is a type that has built-in functionality for the addition operator
without overloads (arithmetic, pointer, member pointer, enum and
std::nullptr_t).
It inherits from integral_constant as being either true_type or
false_type, depending on whether T is a scalar type, no matter its
const and/or volative qualification.
It means pointer is scalar type.
Now if we go to definition of literal type:
A type is a literal type if it is:
a scalar type; or
a reference type; or
an array of literal type; or
-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 literal types.
Now, combining above 2 statements, it means pointer is literal type. However pointer can not be constexpr. can someone please clarify?
further see following code:
int a = 7;
constexpr int *pointer1 = &a;
int main ()
{
int b = 4;
constexpr int *pointer2 = &b;
}
pointer1 is fine but pointer 2 gives error. does that mean pointer to global is fine but to automatic variable is not? Does standard mention this anywhere ?
Pointers are literal types. They can be constexpr under certain conditions:
[expr.const] 6
... [a pointer is constexpr if] it contains the address of an object with static storage duration, the address past the end of such an object (5.7), the address of a function, or a null pointer value.
(Where "object with static storage duration" means a global or static object, or a subobject of such object.)
A demo:
int x;
int main()
{
constexpr int *ptr = &x; // Compiles.
// Doesn't compile: `error: '& foo' is not a constant expression`
// int foo;
// constexpr int *bar = &foo;
}
Apparently GCC (with -pedantic-errors -std=c++11/14/17) happily accepts out-of-range constexpr pointer arithmetic: constexpr int *ptr = &x - 10;, which seems like a bug to me.
Yes they are literals; yes they can be constexpr. To demonstrate:
constexpr int* foo=0;
int x = 7;
constexpr int* ptr=&x;
int main(){}
I'm trying to implement compile-time validation of some hardcoded values. I have the following simplified attempt:
using Type = std::initializer_list<int>;
constexpr bool all_positive(const Type& list)
{
bool all_positive = true;
for (const auto& elem : list)
{
all_positive &= (elem > 0);
}
return all_positive;
}
int main()
{
static constexpr Type num_list{ 1000, 10000, 100 };
static_assert(all_positive(num_list), "all values should be positive");
return 0;
}
gcc compiles this and works exactly as I expected, but clang fails compilation with the error:
static_assert_test.cc:79:16: error: static_assert expression is not an integral constant expression
static_assert(all_positive(num_list), "all values should be positive");
^~~~~~~~~~~~~~~~~~~~~~
static_assert_test.cc:54:20: note: read of temporary is not allowed in a constant expression outside the expression that created the temporary
all_positive &= (elem > 0);
^
static_assert_test.cc:79:16: note: in call to 'all_positive(num_list)'
static_assert(all_positive(num_list), "all values should be positive");
^
static_assert_test.cc:77:32: note: temporary created here
static constexpr Type num_list{ 1000, 10000, 100 };
What's the expected behaviour here? Should this compile or not? And if not, is there an alternative way to validate hard-coded values?
As Yola's answer says, a temporary array is created, and the expression elem > 0 tries to apply a lvalue-to-rvalue conversion to elem. Now we refer to the standard [expr.const]/2:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
...
an lvalue-to-rvalue conversion unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
a non-volatile glvalue that refers to a subobject of a string literal, or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
...
Note the first bullet does not apply here because elem does not refer to a complete object (it is a subobject of an array). The third bullet does not apply too because the temporary array is not defined with constexpr though it is a const object. As a result, all_positive(num_list) fails to become a constant expression.
The key is that accessing an element of a const, but not constexpr, array is not permitted in a constant expression, though the values of these elements may be able to be determined at compile time. The following code snippet shows this issue:
const int ci[1] = {0};
const int &ri = ci[0];
constexpr int i = ri; // error
The problem is that you are trying to use temporary array to initialize your constexpr.
An object of type std::initializer_list is constructed from an initializer list as if the implementation
generated and materialized (7.4) a prvalue of type “array of N const E”, where N is the number of elements
in the initializer list.
But this temporary array is not a constant per se. It could work like this:
static constexpr array<int,4> arr = { 1000, 10000, 100 };
static constexpr Type num_list(&arr[0], &arr[3]);
static_assert(all_positive(num_list), "all values should be positive");