llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.84k stars 11.47k forks source link

Clang can't subtract 2 pointers into the same multidimensional array at compile time #94594

Open tbaederr opened 3 months ago

tbaederr commented 3 months ago

Take this example:

struct A {};
constexpr A a[3][3];
constexpr int diff2 = &a[1][3] - &a[0][0];

clang prints:

<source>:7:15: error: constexpr variable 'diff2' must be initialized by a constant expression
    7 | constexpr int diff2 = &a[1][3] - &a[0][0];
      |               ^       ~~~~~~~~~~~~~~~~~~~
<source>:7:32: note: subtracted pointers are not elements of the same array
    7 | constexpr int diff2 = &a[1][3] - &a[0][0];
      |        

but all other compilers seem to accept this code just fine: https://godbolt.org/z/f7bGEThTT

llvmbot commented 3 months ago

@llvm/issue-subscribers-clang-frontend

Author: Timm Baeder (tbaederr)

Take this example: ```c++ struct A {}; constexpr A a[3][3]; constexpr int diff2 = &a[1][3] - &a[0][0]; ``` clang prints: ```console <source>:7:15: error: constexpr variable 'diff2' must be initialized by a constant expression 7 | constexpr int diff2 = &a[1][3] - &a[0][0]; | ^ ~~~~~~~~~~~~~~~~~~~ <source>:7:32: note: subtracted pointers are not elements of the same array 7 | constexpr int diff2 = &a[1][3] - &a[0][0]; | ``` but all other compilers seem to accept this code just fine: https://godbolt.org/z/f7bGEThTT
tbaederr commented 3 months ago

Also fails with integers:

constexpr int a[3][3] ={};
constexpr int diff2 = &a[1][3] - &a[0][0];
frederick-vs-ja commented 3 months ago

Hmm, IIUC the subexpression a[1][3] already results in UB, so this ought to fail to compile. CWG2875 clarifies this.

~The current title "Clang can't compare 2 pointers into the same multidimensional array at compile time" doesn't match the example.~ The comparison is OK with Clang (Godbolt link) despite that the example should also be rejected.

struct A {};
constexpr A a[3][3];
constexpr bool is_greater = &a[1][3] > &a[0][0]; // UB -> ill-formed; a[1] + 3 > &a[0][0] is OK
static_assert(is_greater, "");

Assuming &a[1][3] is replaced with a[1] + 3 (such transformation is required in C), the subtraction results in UB due to [expr.add]/5. These two pointers point to elements of different arrays, although the pointed to arrays are themselves elements of the same enclosing array.

tbaederr commented 3 months ago

Hmm, IIUC the subexpression a[1][3] already results in UB, so this ought to fail to compile

It's a one-past-the-end pointer which is fine to build an address to AFAIK. It's also accepted by all compilers.

frederick-vs-ja commented 3 months ago

Hmm, IIUC the subexpression a[1][3] already results in UB, so this ought to fail to compile

It's a one-past-the-end pointer which is fine to build an address to AFAIK. It's also accepted by all compilers.

It's OK to build such an address, but a[1][3] additionally performs dereferencing via the the built-in operator[], and CWG2823 clarified that such dereferencing is UB.

C DR076/WG14 N721 made &a[1][3] equivalent to a[1] + 3 and thus well-defined, but such equivalence isn't established in C++.

I attempted to submit an issue to apply C DR076 to C++, but the attempt was rejected. See cplusplus/CWG#464.

zygoloid commented 1 month ago

The comparison is OK with Clang (Godbolt link) despite that the example should also be rejected.

I think the comparison is fine; per [expr.rel]/4, "If two pointers point to different elements of the same array, or to subobjects thereof, the pointer to the element with the higher subscript is required to compare greater"

But for example:

constexpr int diff2 = &a[1][2] - &a[0][0];

is UB and hence non-constant by [expr.add]/5: "[...] if P and Q point to, respectively, array elements i and j of the same array object x, the expression P - Q has the value i - j [...] otherwise the behavior is undefined"