I was trying to figure out the restrictions of constexpr
in cpp11/14. There are some usage requirements I found in CPP14-5.19-4:
A constant expression is either a glvalue core constant expression whose value refers to an object with static storage duration or to a function, or a prvalue core constant expression whose value is an object where, for that object and its subobjects:
- ...
- if the object or subobject is of pointer type, it contains the address of another 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.
I've run some tests(code shown below) for expressions that involves address-of operator &
, in order to ensure the correctness of the standards' statements quoted above.
Simply put, I tried to take the address of a global int variable global_var
, which is an object with static storage duration(if I was not thinking wrong), everything works just as standards points out. But, what confused me is that, when I tried to assign another pointer-type object(global_var_addr1
in code), which stored the address of the same object global_var
, the program won't compile. And GCC says:
error: the value of ‘global_var_addr1’ is not usable in a constant expression
note: ‘global_var_addr1’ was not declared ‘constexpr’
, while Clang-Tidy says:
error: constexpr variable 'x2' must be initialized by a constant expression [clang-diagnostic-error]
note: read of non-constexpr variable 'global_var_addr1' is not allowed in a constant expression
and I don't know why, is there anything I missed?
So my question is:
1. Why, in a constant expression, I cannot use a pointer-type object which contains the address of an object with static storage duration, as standards says?
2. Why everything goes different in the same context as (1), when the object is auto
specified?
Any advices would be welcomed, thanks in advance!
Code:
const int global_var_c = 123;
int global_var = 123;
const void *global_var_addr1 = &global_var;
const void *global_var_addr2 = nullptr;
auto global_var_addr3 = nullptr;
auto main() -> int
{
constexpr const int x00 = global_var_c; // OK
constexpr const void *x0 = &global_var; // OK
// Operate on the object of pointer type
constexpr const void *x1 = &global_var_addr1; // OK
constexpr const void *x2 = global_var_addr1; // ERROR: read of non-constexpr variable 'global_var_addr1'...
// Operate on nullptr
constexpr const void *x3 = &global_var_addr2; // OK
constexpr const void *x4 = global_var_addr2; // ERROR: read of non-constexpr variable 'global_var_addr2'...
// Operate on nullptr (with type deduction)
constexpr const void *x5 = global_var_addr3; // OK
constexpr const void *x6 = &global_var_addr3; // OK
}
CodePudding user response:
In both
constexpr const void *x2 = global_var_addr1;
and
constexpr const void *x4 = global_var_addr2;
a lvalue-to-rvalue conversion happens from the variable global_var_addr1
/global_var_addr2
glvalue to the pointer value they hold. Such a conversion is only allowed if the variable's lifetime began during the evaluation of the constant expression (not the case here) or if it is usable in constant expressions, meaning that it is constexpr
(not the case here) or initialized by a constant expression (is the case here) and of reference or const
-qualified integral/enumeration type (not the case here).
Therefore the initializers are not constant expressions.
This is different in the case of
constexpr const int x00 = global_var_c;
since global_var_c
is of const
-qualified integral type.
I am not exactly sure about
constexpr const void *x5 = global_var_addr3; // OK
Intuitively it should work, because the type of nullptr
and consequently the deduced type of global_var_addr3
is std::nullptr_t
which doesn't need to carry any state, so that a lvalue-to-rvalue conversion wouldn't be necessary. Whether the standard actually guarantees that, I am not sure at the moment.
Reading the current wording (post-C 20 draft), [conv.ptr] specifies only conversion of a null pointer constant (i.e. a prvalue of std::nullptr_t
) to another pointer type and [conv.lval] specifically states how the lvalue-to-rvalue conversion of std::nullptr_t
produces a null pointer constant. [conv.lval] also clarifies in a note that this conversion doesn't access memory, but I don't think that makes it not a lvalue-to-rvalue conversion given that it still written under that heading.
So it seems to me that strictly reading the standard
constexpr const void *x5 = global_var_addr3; // OK
should be ill-formed (whether global_var_addr3
is const
-qualified or not).
Here is an open clang bug for this. There seems to be a link to come internal discussion by the standards committee, which I cannot access.
In any case, the auto
placeholder doesn't matter. You could have written std::nullptr_t
for it instead directly.
All of these are requirements for being a core constant expression, which is a prerequisite to the requirements you mention in your question.
CodePudding user response:
The variable declared here is clearly not constexpr
(nor even const
):
const void *global_var_addr1 = &global_var;
And you can't use non-constexpr values to initialize constexpr values. So it's no surprise this fails to compile:
constexpr const void *x2 = global_var_addr1; // ERROR: read of non-constexpr
The address of a non-constexpr value can be used in cases like you've shown, however, but the value stored in a variable and the address of a variable are not the same thing.