Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is 0 == ("abcde"+1) not a constant expression?

Why doesn't the following code compile?

// source.cpp

int main()
{
   constexpr bool result = (0 == ("abcde"+1));
}

The compile command:

$ g++ -std=c++14 -c source.cpp

The output:

source.cpp: In function ‘int main()’:
source.cpp:4:32: error: ‘((((const char*)"abcde") + 1u) == 0u)’ is not a constant expression
 constexpr bool result = (0 == ("abcde"+1));
                         ~~~^~~~~~~~~~~~~~~

I'm using gcc6.4.

like image 925
embedc Avatar asked Jan 29 '19 15:01

embedc


1 Answers

The restrictions on what can be used in a constant expression are defined mostly as a list of negatives. There's a bunch of things you're not allowed to evaluate ([expr.const]/2 in C++14) and certain things that values have to result in ([expr.const]/4 in C++14). This list changes from standard to standard, becoming more permissive with time.

In trying to evaluate:

constexpr bool result = (0 == ("abcde"+1));

there is nothing that we're not allowed to evaluate, and we don't have any results that we're not allowed to have. No undefined behavior, etc. It's a perfectly valid, if odd, expression. Just one that gcc 6.3 happens to disallow - which is a compiler bug. gcc 7+, clang 3.5+, msvc all compile it.


There seems to be a lot of confusion around this question, with many comments suggesting that since the value of a string literal like "abcde" is not known until runtime, you cannot do anything with such a pointer during constant evaluation. It's important to explain why this is not true.

Let's start with a declaration like:

constexpr char const* p = "abcde";

This pointer has some value. Let's say N. The crucial thing is - just about anything you can do to try to observe N during constant evaluation would be ill-formed. You cannot cast it to an integer to read the value. You cannot compare it to a different, unrelated string (by way of [expr.rel]/4.3):

constexpr char const* q = "hello";
p > q; // ill-formed
p <= q; // ill-formed
p != q; // ok, false

We can say for sure that p != q because wherever it is they point, they are clearly different. But we cannot say which one goes first. Such a comparison is undefined behavior, and undefined behavior is disallowed in constant expressions.

You can really only compare to pointers within the same array:

constexpr char const* a = p + 1; // ok
constexpr char const* b = p + 17; // ill-formed
a > p; // ok, true

Wherever it is that p points to, we know that a points after it. But we don't need to know N to determine this.

As a result, the actual value N during constant evaluation is more or less immaterial.

"abcde" is... somewhere. "abcde"+1 points to one later than that, and has the value "bcde". Regardless of where it points, you can compare it to a null pointer (0 is a null pointer constant) and it is not a null pointer, hence that comparison evaluates as false.

This is a perfectly well-formed constant evaluation, which gcc 6.3 happens to reject.


Although we simply state by fiat that std::less()(p, q) provides some value that gives a consistent total order at compile time and that it gives the same answer at runtime. Which is... an interesting conundrum.

like image 136
Barry Avatar answered Nov 12 '22 13:11

Barry