Consider the following code:
struct S
{
constexpr S(){};
constexpr S(const S &r) { *this = r; };
constexpr S &operator=(const S &) { return *this; };
};
int main()
{
S s1{};
constexpr S s2 = s1; // OK
}
The above program is well-formed but I'm expecting it to be ill-formed, because [expr.const]/(5.16) says:
An expression E is a core constant expression unless the evaluation of
E, following the rules of the abstract machine ([intro.execution]),
would evaluate one of the following:
[..]
(5.16) a modification of an object ([expr.ass], [expr.post.incr], [expr.pre.incr]) unless it is applied to a non-volatile lvalue of
literal type that refers to a non-volatile object whose lifetime began
within the evaluation of E;
[..]
Given the expression E is s1. The expression E evaluates a modification of the object *this. The modification is applied to the non-volatile lvalue *this which is of literal type, and this lvalue refers to a non-volatile object which is s1 but the lifetime of s1 does not begin within the evaluation of E: That's the lifetime of the object s1 began before the evaluation of the expression E.
So I'm expecting the program is ill-formed because the "unless" part does not satisfy which means, to me, that the expression E is not a core constant expression. So what I'm missing here? Am I misreading (5.16)?
As Language Lawyer points out, there are no modifications in your program, however even if the constructor did modify s2, this wouldn't necessarily make the program ill-formed, because:
In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression ([expr.const]).
-- [dcl.constexpr]/7
The constructor is allowed to modify s2 because the lifetime of s2 begins within the full-expression of the initialization, and only the full-expression of the initialization is required to be a constant expression (not the initializer in isolation).
Addendum: The only modifications that are relevant for [expr.const]/5.16 are modifications to scalar objects. This is because only modifications to scalar objects actually result in accesses to the memory of the abstract machine (see also defns.access). When I say the constructor is allowed to modify s2, I mean the constructor is allowed to modify scalar subobjects of s2.
Related
constexpr int func(int const& rf){
return rf;
}
int main(){
constexpr int value = func(0);
}
Consider the above code, the variable value shall be initialized by a constant expression, which is func(0), which firstly shall be a core constant expression. To determine whether the expression func(0)is a core constant expression, the following rules will be applied to it, that is:
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
[...], 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;
Despite the lvalue-to-rvalue conversion is applied to rf and such conversion satisfied the bullet (2.7.4), however, take a look to the next paragraph, that is:
expr.const#2.11
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
(2.11.1) it is initialized with a constant expression or,
(2.11.2) its lifetime began within the evaluation of e;
I don't know what actually the phrase preceding initialization mean? Does it mean that a variable should be initialized before using it, or it means that in a declaration of a variable shall have an initializer. Anyhow, before applying the lvalue-to-rvalue conversion to glvalue rf, the glvalue rf should be evaluated to determine the identity of an object, which is ruling by:
A glvalue is an expression whose evaluation determines the identity of an object, bit-field, or function.
That means not only bullet [expr.const#2.7] shall be obeyed but also [expr.const#2.11] shall be obeyed.
Because id-expression rf is of reference type. So, in order to make the expression func(0) be a core constant expression, the id-expression rf must have a preceding initialization, and satisfies at least one of bullet (2.11.1) and (2.11.2). In my example, the bullet (2.11.2) is obeyed by rf and the compiler agree that func(0) is a constant expression. The outcome is here, the outcome seems to evidence that preceding initialization means be initialized rather than have an initializer due to the parameter declaration does not have an initializer in the first example.
In order to check such a thought, I test the below code:
constexpr int func(int const& rf){
constexpr int const& v = rf;
return 0;
}
int main(){
static int const data = 0;
constexpr int value = func(data);
}
The outcomes of the compilers point out rf is not a constant expression. I'm confuse at this outcome. According to the above supposition. rf has a preceding initialization, and the bullet (2.11.1) is obeyed because data is a constant expression, even if the bullet (2.11.2) does not be satisfied.
So, I wonder what actually does the phrase preceding initialization mean? If it means that a declaration for a variable has an initializer, how could the expression func(0) in the first example be a constant expression?
The outcome seems to evidence that "preceding initialization" means "be initialized" rather than have an initializer due to the parameter declaration does not have an initializer in the first example.
It does mean "be initialized", but it's more importantly about the visibility of a preceding initialization within the context of the expression being evaluated. In your example, in the context of evaluating func(0) the compiler has the context to see the initialization of rf with 0. However, in the context of evaluating just the expression rf within func, it doesn't see an initialization of rf. The analysis is local, as in it doesn't analyze every call-site. This leads to expression rf itself within func not being a constant expression while func(0) is a constant expression.
If you were to write this instead:
constexpr int func(int const& rf) {
/* constexpr */ int const& v = rf;
return v;
}
int main() {
static int const data = 0;
constexpr int value = func(data);
}
This is okay again because in the context of func(data), rf has a preceding initialization with a constant expression data, and v has a preceding initialization with rf which is not a constant expression but its lifetime began within the evaluation of func(data).
This is CWG2186:
2186. Unclear point that “preceding initialization” must precede
Similar to the concern of issue 2166, the requirement of 8.20 [expr.const] bullet 2.7.1 for
— 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
does not specify the point at which the determination of “preceding initialization” is made: is it at the point at which the reference to the variable appears lexically, or is it the point at which the outermost constant evaluation occurs? There is implementation divergence on this point.
But the meaning should be "lexically", because ODR.
#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
The expression b in this code shall be a core constant expression
int main()
{
constexpr int a = 10;
const int &b = a;
constexpr int c = b; // Here
return 0;
}
since the standard says (8.20, paragraph 2 [expr.const] in n4700)
An expression e is a core constant expression unless the evaluation of
e would evaluate one of the following expressions:
...
an lvalue-to-rvalue conversion (7.1) unless it is applied to
...
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
First, the expression b in the above code is an lvalue (which is also a glvalue) since it's a reference, thereby being a variable (8.1.4.1, paragraph 1
[expr.prim.id.unqual]):
The expression is an lvalue if the entity is a function,
variable, or data member and a prvalue otherwise; it is a bit-field if the identifier designates a bit-field (11.5).
Second, the object the variable b denotes is a, and it's declared with constexpr. However, GCC complains:
./hello.cpp: In function ‘int main()’:
./hello.cpp:6:20: error: the value of ‘b’ is not usable in a constant expression
constexpr int c = b;
^
./hello.cpp:5:13: note: ‘b’ was not declared ‘constexpr’
const int &b = a;
As far as I can tell, a reference is not an object, so the above bullet apparently suggests that a shall be declared with constexpr. Am I missing something?
The reason why I don't agree with GCC is that GCC sees b as an object, thereby requiring it to be declared with constexpr. However, b is not an object!
One of the rules for core constant expressions is that we can't evaluate:
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;
b is an id-expression that refers to a variable of reference type with preceding initialization. However, it is initialized from a. Is a a constant expression? From [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), or a prvalue core constant expression whose value satisfies the following constraints: [... ]
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.
a is a glvalue core constant expression (it doesn't hit any of the restrictions in expr.const/2), however it is not an object with static storage duration. Nor is it a function.
Hence, a is not a constant expression. And b, as a result, isn't initialized from a constant expression and so can't be used in a core constant expression. And thus c's initialization is ill-formed as not being a constant expression. Declare a as a static constexpr int, and both GCC and Clang accept the program.
C++, you magical beast.
All quotes given in this answer are from the current working draft.
Given your example:
int main()
{
constexpr int a = 10;
const int &b = a;
constexpr int c = b; // here
return 0;
}
First of all, let's take your example line by line. The first line is a definition of a constexpr variable named by identifier a:
constexpr int a = 10;
The initializer 10 is an integral constant expression, hence it's a core constant expression, per [expr.const]/9:
An 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. [ Note: Such
expressions may be used as bit-field lengths, as enumerator
initializers if the underlying type is not fixed, and as alignments.
— end note ]
And it's a constant expression because it's a prvalue core constant expression that satisfies the [expr.const]/12 constraints:
A constant expression is [..] a prvalue core constant expression
whose value satisfies the following constraints:
(12.1) 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,
(12.2) 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 non-immediate function, or a null pointer value
(12.3)
if the value is of pointer-to-member-function type, it does not
designate an immediate function, and
(12.4) if the value is an object
of class or array type, each subobject satisfies these constraints for
the value.
Hence, the initializer expression 10 is a prvalue core constant expression that's neither a pointer type nor a class type nor an array type. Hence, it's a constant expression.
The second line declares/defines a reference to a named by identifier b:
const int &b = a;
In order to know whether the initializer expression a is a core constant expression, you have to invoke [expr.const]/5:
An expression E is a core constant expression unless the evaluation
of E, following the rules of the abstract machine ([intro.execution]),
would evaluate one of the following: [..]
At this point, the evaluation of E does not evaluate any of the constraints defined in [expr.const]/5. Therefore, the expression E is a core constant expression.
But being E is a glvalue core constant expression that doesn't mean that E is also constant expression; so you have to check [expr.const]/12:
A constant expression is either a glvalue core constant expression
that refers to an entity that is a permitted result of a constant
expression [..]
An entity is a permitted result of a constant expression if it is an
object with static storage duration that either is not a temporary
object or is a temporary object whose value satisfies the above
constraints, or if it is a non-immediate function.
So the entity a is not a permitted result of constant expression because that entity neither refers to an object with static storage duration nor a temporary object or non-immediate function. Therefore, the glvalue core constant expression a is not a constant expression.
The third line defines an constexpr variable named by identifier c initialized by the variable b:
constexpr int c = b;
Again you have to invoke [expr.const]/5 in order to know whether the expression b is a core constant expression or not. As you already noticed, the bullet (5.8) maybe satisfied since the initialization of c involves an lvalue-to-rvalue conversion. So per [expr.const]/5
An expression E is a core constant expression unless the evaluation of
E [..] would evaluate one of the following:
(5.8) an lvalue-to-rvalue conversion unless it is applied to
(5.8.1) a non-volatile glvalue that refers to an object that is usable in constant expressions, or
(5.8.2) a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
Note that the document to which I refer doesn't have the following bullet. But C++20 documents have it. And I don't know why it's removed from the current working draft. ([expr.const]/(5.12)):
(5.12) an id-expression that refers to a variable or data member of
reference type unless the reference has a preceding initialization and
either
(5.12.1) it is usable in constant expressions or
(5.12.2) its
lifetime began within the evaluation of E;
Even if it's checked, the expression E is still not a core constant expression because the reference is not usable in constant expressions (see below) and its lifetime doesn't begin within the evaluation of E.
Back to our explanation. The bullet (5.8) is satisfied since the expression E evaluates an lvalue-to-rvalue conversion. So (5.8.1) is tried first.
Indeed, the lvalue-to-rvalue conversion is applied to a non-volatile glvalue (b). But, Is this glvalue refers to an object that's usable in constant expressions? First, you have to check whether it's a constant-initialized variable, then also you have to check whether it's a potentially-constant variable, so [expr.const]/2 is checked first:
A variable or temporary object o is constant-initialized if
(2.1) either it has an initializer or its default-initialization results in some initialization being performed, and
(2.2) the full-expression of its initialization is a constant expression when interpreted as a constant-expression [..]
The first bullet (2.1) is satisfied because the variable b has an initializer. But the second bullet isn't satisfied because the full-expression of its initialization is not a constant expression because its initializer a is not a constant expression as aforementioned. Therefore, the variable b is not constant-initialized.
Just for completeness, the [expr.const]/3 is tried:
A variable is potentially-constant if it is constexpr or it has a
reference or const-qualified integral or enumeration type.
Since the variable b has a reference type, it's considered to be potentially-constant. Note, there's no need to check whether the variable b is potentially-constant because intuitively you can't even enter the [expr.const]/4 since it requires the variable to be constant-initialized:
A constant-initialized potentially-constant variable is usable in
constant expressions at a point P if its initializing declaration D is
reachable from P [..]
(emphasis mine)
Even though the variable is potentially-constant, it's not usable in a constant expression because it's not constant-initialized. So we ended up with that the variable b is not usable in constant expressions (as the GCC diagnostic hints to you). Therefore, the bullet (5.8.1) is not satisfied: Hence, the expression E is not a core constant expression and therefore not a constant expression.
Consider this example from cppreference:
struct S { static const int x = 1; };
void f() { &S::x; } // discarded-value expression does not odr-use S::x
I agree that &S::x is a discarded-value expression, since the standard says (9.2, paragraph 1 [stmt.expr] from n4700)
Expression statements have the form
expression-statement:
expression_opt ;
The expression is a discarded-value expression (Clause 8)...
However, is that enough for S::x to not be odr-used? 6.2, paragraph 3 [basic.def.odr] states
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless
...
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 (Clause 8).
The problem is that the discarded-value expression &S::x has no potential results (which means that S::x is not a potential result of &S::x), as you can see from 6.2, paragraph 2 [basic.def.odr]:
... The set of potential results of an expression e is defined as follows:
If e is an id-expression (8.1.4), the set contains only e.
If e is a subscripting operation (8.2.1) with an array operand, the set contains the potential results of that operand.
...
Otherwise, the set is empty.
Then, how can you explain that S::x is not odr-used?
It is indeed odr-used. Your analysis is correct (and I fixed that example a while ago).
Yes, in the example, &S::x odr-uses S::x.
[basic.def.odr]/4
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression 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 is applied to e, or e is a discarded-value expression.
The address of an object is never a constant expression. That's why S::x is odr-used in &S::x.
To justify that last assertion:
[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), or a prvalue core constant expression whose value satisfies the following constraints [...]
and
[expr.const]/2.7
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:
[...]
2.7) an lvalue-to-rvalue conversion unless it is applied to
(none of the following points applies:)
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, 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 subobject 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;
When declaring const int, it may be entirely discarded by the compiler unless you using its address. taking the address is not enough.
Discarded is not mean the value is not evaluated, it is, but it mean there is no memory address containing the const value, the compiler just replace the const variable by its value, as it was just a macro.
In addition, when taking a pointer to it and taking back the value from the pointer, doesn't impress the compiler much, it just ignores it and use the value.
The following code shows it, this code can be compiled and run (I test it by several compilers, I'm still not sure if it successfully compiled by all...) despite of the fact that S::x was not declared:
#include <iostream>
using namespace std;
struct S
{
static const int x=0;
};
//const int S::x; //discarded
int main()
{
const int *px = &S::x; //taking the address
cout<< *px <<endl; //print the value - OK
return 0;
}
But if I'll try to use the address itself(not the value) like:
cout<< px <<endl; //print the address - Will be failed
the link will failed dou to: "undefined reference to S::x".
Therefore, my conclusion is: taking an address without using it, doesn't count at all.
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.