Both clang
and g++
seem to be compliant with the last version of the paragraph [expr.const]/5 in the C++ Standard. The following snippet prints 11 for both compilers. See live example:
#include <iostream>
void f(void) {
static int n = 11;
static int* temp = &n;
static constexpr int *&&r = std::move(temp);
std::cout << *r << '\n';
}
int main()
{
f();
}
According to my understanding of this paragraph both compilers should print 2016
for the code below. But they don't. Therefore, I must conclude that the code shows undefined behavior, as clang
prints an arbitrary number and g++
prints 0
. I'd like to know why is it UB, taking into consideration, for example, the draft N4527 of the Standard? Live example.
#include <iostream>
void f(void) {
static int n = 11;
static int m = 2016;
static int* temp = &n + 1;
static constexpr int *&&r = std::move(temp);
std::cout << *r << '\n';
}
int main()
{
f();
}
Edit
I have a habit of not being satisfied with an answer that just says the code is UB, or shows undefined behavior. I always like to investigate a little deeper, and sometimes, as now, I happen to be lucky enough to understand a little bit more, how compilers are built. And that's what I found out in this case:
Both clang
and GCC
seem to eliminate any unused variable, like m
, from the code, for any optimization level greater than -O0
. GCC
seems to order local variables with static storage duration, the same way variables are placed on the stack, i.e., from higher to lower addresses.
Thus, in clang
, if we change the optimization level to -O0
we get the number 2016
printed as expected.
In GCC
, if in addition to that, we also change the definition of
static int* temp = &n + 1;
to
static int* temp = &n - 1;
we will also get the number 2016
printed by the code.
I don't think there's anything subtle here. &n + 1
points one-past-the-end of the array-of-one as which you may consider the location n
, and so it does not constitute a dereferenceable pointer, although it is a perfectly valid pointer. Thus temp
and r
are perfectly fine constexpr variables.
You could use r
like this:
for (int * p = &n; p != r; ++p) { /* ... */ }
This loop could even appear in a constexpr function.
The behaviour is of course undefined when you attempt to dereference r
, but that has nothing to do with constant expressions.
You've apparently expected that you can:
This is nonsense.
You'd have to eschew all standard-backed guarantees, relying only on an unholy combination of UB and implementation documentation. Clearly you have crossed the UB threshold long before we ever even entertain discussions about constexpr
and std::move
, so I'm not sure what relevance they were intended to hold in this question.
Pointers are not "memory addresses" that you can use to navigate your declaration space.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With