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");
Related
Why does it working?
#include <cstdio>
template<auto x> struct constant {
constexpr operator auto() { return x; }
};
constant<true> true_;
static constexpr const bool true__ = true;
template<auto tag> struct registryv2 {
// not constexpr
static auto push() {
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
//*
return true_; // compiles
/*/
return true__; // read of non-const variable 'x' is not allowed in a constant expression
//*/
}
// not constexpr either
static inline auto x = push();
};
static_assert(registryv2<0>::x);
https://godbolt.org/z/GYTdE3M9q
static_assert evaluates non constant expression
Nope, it most certainly does not. Constant evaluation has a strict set of conditions to is must obey in order to succeed.
For starters:
[dcl.dcl]
6 In a static_assert-declaration, the constant-expression shall be a contextually converted constant expression of type bool.
"contextually converted" is standard lingo for "we'll consider explicit conversion operators". The place where it may become counter-intuitive is when "converted constant expression" is defined.
[expr.const]
4 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
user-defined conversions,
[...]
The fine point is in the first sentence of the paragraph. The converted expression must be a constant expression. But the source expression doesn't have to be! So long as the conversion sequence is limited to the list in the paragraph and is valid constant evaluation itself, we are in the clear. In your example, the expression registryv2<0>::x has type constant<true>, it can be contextually converted to a bool via the user defined conversion operator. And well, the conversion operator satisfiers all the requirements of a constexpr function and constant evaluation.
The list of requirements for constant evaluation is rather long so I won't go over it to verify every bullet is upheld. But I will demonstrate that we can trip one of them.
template<auto x> struct constant {
bool const x_ = x;
constexpr explicit operator auto() const { return x_; }
};
This change immediately makes the godbolt code sample ill-formed. Why? Because we are doing an lvalue-to-rvalue conversion (standard lingo for access) on a bool when it is not permitted.
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;
Going over the exceptions, none apply. So now registryv2<0>::x is not a contextually converted constant expression of type bool.
This also explain why true__1 is verboten. Same issue, access to an object that is disallowed.
1 - That's a reserved identifier. Two consecutive underscores belong to the implementation for any use. Not critical to the issue at hand, but take note.
It works because this conversion to auto operator is constexpr.
template<auto x> struct constant {
constexpr explicit operator auto() const { return x; }
};
It is called by the static_assert, even if marked explicit. It looks like putting an expession within a static_assert is akin to making an explicit cast to bool. The same is true for 'if' as in if (registryv2<0>::x)
#include <iostream>
constexpr int func2(int const& id){
return id;
}
template<int v>
struct Test{
};
int main(){
const int v = 0;
Test<func2(v)> c;
}
Consider the above code,I just don't understand why the code is well-formed.My pointview is that the name v is used as a glvalue when evalute expression func2,becuase the parameter of func2 is of reference type,the v need to be bound to the id-expression id.So we look at the requirement of a glvalue constant expression,here are quotes about that.
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.
We ignore the case of prvalue,because here v is used as a glvalue.
An entity is a permitted result of a constant expression is:
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.
In my program portion,The const int v = 0; does not have static storage duration,it just has automatic storage duration.So when evaluting the expression func2(v) to determine whether it is a constant expression,Firstly,the v must be a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression,therefore,why the program is well-formed here?If I lose any important quote,Please correct me.
We ignore the case of prvalue, because here v is used as a glvalue
Is it though? This is an example from cppreference:
void test() {
static const int a = std::random_device{}();
constexpr const int& ra = a; // OK: a is a glvalue constant expression
constexpr int ia = a; // Error: a is not a prvalue constant expression
const int b = 42;
constexpr const int& rb = b; // Error: b is not a glvalue constant expression
constexpr int ib = b; // OK: b is a prvalue constant expression
}
And yes, const int b = 42 is rather weird here because technically speaking, you can bind b to const int&, const_cast the const away and assign a runtime value to it. However, considering what is an integral constant expression and what are the requirements of a const object it makes perfect sense:
Integral constant expression is an expression of integral or unscoped
enumeration type implicitly converted to a prvalue, where the
converted expression is a core constant expression. If an expression
of class type is used where an integral constant expression is
expected, the expression is contextually implicitly converted to an
integral or unscoped enumeration type.
Variable b sure does look like something you could implicitly convert to a prvalue constant expression because it basically serves as an alias for literal 42 in this context and integer literals are prvalues by definition.
Now for the problematic part - this:
const object - an object whose type is const-qualified, or a
non-mutable subobject of a const object. Such object cannot be
modified: attempt to do so directly is a compile-time error, and
attempt to do so indirectly (e.g., by modifying the const object
through a reference or pointer to non-const type) results in undefined
behavior.
And:
A core constant expression is any expression whose evaluation would
not evaluate any one of the following:
...
an expression whose evaluation leads to any form of core language
undefined behavior (including signed integer overflow, division by
zero, pointer arithmetic outside array bounds, etc). Whether standard
library undefined behavior is detected is unspecified.
Means that as soon as you start doing funny things with that b, you can expect anything to happen. For example, this is what I tried doing to your code in latest MSVC with all standard-conformance options switched on:
#include <iostream>
#include <random>
constexpr int func2(int const& id) {
return id;
}
template<int v>
struct Test {
long array[v];
};
int main() {
const int v = 0;
const int& ref = v;
const_cast<int&>(ref) = std::random_device()() % std::numeric_limits<int>::max();
Test<func2(v)> c;
return 0;
}
With language extensions turned on I got a C4200: nonstandard extension used : zero-sized array in struct/union warning. After switching them off, the program wouldn't compile. And when I deleted the array part from the struct it started compiling again.
I try to answer this question.why the func2(v) is a constant expression,Becuase For expression func2(v),when evaluting this postfix-expression,there's no requirement that v must be a glvalue constant expression in the list of "would evaluate one of the following expressions:",Even,these rules does not mandate that the one expression within a potentially core constant expression would be a glvalue constant expression,only require the expression does not violate the listed requirement.So let's continue,When initialization of the parameter,It's another rule here:
A full-expression is:
[...]
an init-declarator or a mem-initializer, including the constituent expressions of the initializer
So,when evalute this full-expression,it only does not violate these listed condition,then the func2(v) would be evaluted as a constant expression,So let's look at these rules:
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or,
its lifetime began within the evaluation of e;
For id-expression id,its preceding initialization is the corresponding argument,Because of this rule:
When a function is called, each parameter ([dcl.fct]) shall be initialized ([dcl.init], [class.copy], [class.ctor]) with its corresponding argument.
So,the first condition is true.And "it is initialized with a constant expression" is false,the condition "its lifetime began within the evaluation of e" is true.In conlusion,the expression func2(v) indeed a constant expression
I have two snippets.
The first snippet:
#include <string>
template <typename T>
constexpr bool foo(T&&) {
return false;
}
int main() {
std::string a;
if constexpr (foo(a)) {
}
}
The second snippet:
#include <string>
template <typename T>
constexpr bool foo(T&&) {
return false;
}
int main() {
std::string a;
std::string& x = a;
if constexpr (foo(x)) {
}
}
The first one compiles, but the second one does not compile (error message: error: the value of ‘x’ is not usable in a constant expression. Why? Why a is usable in a constant expression and x is not?
The command, used to compile g++ -std=c++17 main.cpp.
Because usually a constant expression cannot evaluate a reference that refers to an object with automatic storage duration. Here I mean "evaluate" by determining the identity of the object, not by determining the value of the object. So even the value of the object a is not required in your example (i.e. no lvalue-to-rvalue conversion is applied), foo(x) is still not a constant expression.
Note foo(a) does not evaluate any reference. Although the parameter of foo is a reference, it is not evaluated as an expression. In fact, even if it was evaluated, for example,
template <typename T>
constexpr bool foo(T&& t) {
t;
return false;
}
foo(a) is still a constant expression. Such cases are exceptions as the reference t is initialized within the evaluation of foo(a).
Related part in the standard (irrelevant part is elided by me):
[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 id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e;
...
[expr.const]/6:
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), ...
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.
Because you can't know at compile time what value x will have, all you know is that it will point to a. What you are doing is checking the "value" of x, but the value of x is the address where a is and you can't know where a will be allocated nor an address is constant.
On the other hand you already know the value of a, it's an empty std::string.
This question contains some more details: how to initialize a constexpr reference
Firstly, In your both code sections, the evaluation context all require the postfix-expression to be a constant expression. The definition of function template foo satisfies the requirements of a constexpr function due to its parameter is of literal type.
id-expression a and x are all glvalue which evaluation determine the identity of an object, but the only difference is that, In your fist code. copy-initialize the parameter from a only require identity conversion, because of the following rules:
When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion
And it does nothing, due to
[over.ics.scs#2]
As described in Clause [conv], a standard conversion sequence is either the Identity conversion by itself (that is, no conversion)
And the remain expressions are all constant expressions. So, for foo(a) this expression, it's a constant expression.
For foo(x), because x is an id-expression of reference type that subject to this rule:
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e;
The lifetime of x began before the evaluation of foo(x) and it isn't initialized with a constant expression because std::string a; is not a glvalue constant expression. If you modify the std::string a; to static std::string a;,then it will be ok.
The code below compiles in GCC, clang and VS2017 and the expression a->i in the return statement is replaced by its constant value 1. Is it correct to say that this is valid because a is not odr-used in the expression a->i?.
struct A
{
static const int i = 1;
};
int f()
{
A *a = nullptr;
return a->i;
}
PS: I believe a is not odr-used in the expression a->i because it satisfies the "unless" condition in [basic.def.odr]/4, as follows:
A variable x whose name appears as a potentially-evaluated
expression ex is odr-used by ex unless applying the
lvalue-to-rvalue conversion (7.1) to x yields a constant expression
(8.6) that does not invoke any non-trivial
functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the
lvalue-to-rvalue conversion (7.1) is applied to e, or e is a
discarded-value expression (8.2).
In particular, the expression ex == a is an element of the set of potential results of the expression e == a->i, according to [basic.def.odr]/2 (2.3), containing the expression ex, where the lvalue-to-rvalue conversion is applied to e.
a is odr-used because you fail the first part of the "unless":
applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.6) that does not invoke any non-trivial functions
Applying the lvalue-to-rvalue conversion to a does not yield a constant expression.
The rest is core issues 315 and 232.
Your analysis is broken in two additional ways:
The "object expression" is defined using the . form of class member access, so you need to rewrite a->i to dot form, i.e., (*a).i, before applying [basic.def.odr]/2.3. a is not a member of the set of potential results of that expression.
That bullet itself is defective because it was written with non-static data members in mind. For static data members, the set of potential results should be in fact the named static data member - see core issue 2353, so a is doubly not a member of the set of potential results of that expression.
[expr.const]/2.7:
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;
[...]
i is a static member of the class... as you can access the static members of the class through using the conventional methods for the instances, they are not tied to any instance in particular, so you don't need to dereference the nullptr pointer (as when you use the sizeof operator). You could also access the field using a simple
return A::i;
statement, because you don't need to create an instance to access it. Indeed, being const, the compiler allows to manage it as a constant value, so only in the case you need to use it's address (by means of the & operator) the compiler can bypass allocating it in read-only memory.
The following sample will probe that:
#include <iostream>
struct A {
static const int i = 1;
};
int main()
{
std::cout << ((A*)0)->i << std::endl;
std::cout << A::i << std::endl;
}
will print
$ a.out
1
1
$ _
Consider the code below:
template<char>
struct S { };
template<int N>
constexpr auto f(const char (&ref) [N]) {
return S<ref[0]>{};
}
int main() {
constexpr auto v = f("foo");
(void)v;
}
It doesn't compile for ref[0] is not a constant expression.
Anyway, the code below compiles fine:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return ref[0];
}
int main() {
constexpr auto v = f("foo");
(void)v;
}
Should both of them compile or fail to do that for more or less the same reason?
From [expr.const] we have that:
A conditional-expression e is a core constant expression unless the evaluation of e [...] would evaluate one of the following expressions:
[...]
- an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
- it is initialized with a constant expression or
- its lifetime began within the evaluation of e;
Anyway, in this case, it is initialized with a constant expression and the lifetime is the same of e, thus the rule doesn't apply.
What's wrong in my reasoning?
As a side question, I would ask if it's possible to use such an array or part of it as a template argument.
This:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return S<ref[0]>{};
}
is ill-formed because according to [temp.arg.nontype]:
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of
the type of the template-parameter.
and from [expr.const]:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the
abstract machine (1.9), would evaluate one of the following expressions: [...]
(2.7) — an lvalue-to-rvalue conversion (4.1) unless it is applied to
(2.7.1) — 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
(2.7.2) — a non-volatile glvalue that refers to a subobject of a string literal (2.13.5), or
(2.7.3) — a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to
a non-mutable sub-object of such an object, or
(2.7.4) — a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within
the evaluation of e;
ref[0] requires an lvalue-to-rvalue conversion and none of those sub-bullets apply. Note that ref is not a string literal, so 2.7.2 doesn't apply, nor is it defined with constexpr, because it's a function argument and we don't have that capability.
We basically need the ability to pass string literals as literals, which doesn't exist yet.
The other example:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return ref[0];
}
doesn't have the converted constant expression required - that one was brought in by the template non-type argument. This code is fine, and would only be problematic if you tried to initialize a constexpr variable with a non-constexpr array value.
The first example shouldn't compile because you cannot have compiletime-only constexpr functions (or overload on compiletime-ness, like D's __cfte).
Following this reasoning, if you called first example's f at runtime, what would its return type be?
As to the side question: Boost Hana, despite supporting only the newest standard, only uses string literals for runtime stuff, so it may not be possible.