llvm / llvm-project

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

[C++] Inconsistent treatment of VLAs in C++23 constexpr/consteval functions #102970

Open MitalAshok opened 1 month ago

MitalAshok commented 1 month ago

Given something like: https://godbolt.org/z/x5n8KbqbW

consteval bool f(int n) {
  int vla[n];
  return true;
}
static_assert(f(1));

This doesn't compile in C++20 because the VLA is not considered a literal type.

However, this does compile in C++23 (https://godbolt.org/z/sdTK7zvxb), with just a warning about the VLA extension.
VLA variables can't be used for much during constant evaluations (sizeof(vla), vla[0] = 1 and (void)vla[0] are all considered invalid subexpressions in constant expressions), so this seems unintentional.

It should probably just be an invalid to flow over the initialization of a VLA during a constant expression in C++23 (and allow if (false) { int vla[n]; } and if !consteval { int vla[n]; }).


On a related note, typedefs for vlas are banned even when not used inside constexpr functions in C++23: https://godbolt.org/z/1vYo9n5qG

constexpr bool f(int n) {
  if !consteval {
    typedef int vla_t[n];
    vla_t vla;
  }
  return true;
}
<source>:3:13: error: variably-modified type 'int[n]' cannot be used in a constexpr function
    3 |     typedef int vla_t[n];
      |             ^~~~~~~~~~~~

At least that sort of use should be supported. This is more questionable:

constexpr bool f(int g()) {
  typedef int vla_t[g()];
  if !consteval { vla_t vla; }
  return true;
}

It could get the same treatment as VLA variables (as long as control doesn't flow over it, so it works in if (false) { typedef int vla_t[g()]; }), or it could "just work" and evaluate the bounds expression during the constant evaluation and discard the result.

The intent of C++23 constexpr declaration rules is that every function can be marked constexpr even if no constant expression can actually be formed.

llvmbot commented 1 month ago

@llvm/issue-subscribers-clang-frontend

Author: Mital Ashok (MitalAshok)

Given something like: https://godbolt.org/z/x5n8KbqbW ```c++ consteval bool f(int n) { int vla[n]; return true; } static_assert(f(1)); ``` This doesn't compile in C++20 because the VLA is not considered a literal type. However, this does compile in C++23 (<https://godbolt.org/z/sdTK7zvxb>), with just a warning about the VLA extension. VLA variables can't be used for much during constant evaluations (`sizeof(vla)`, `vla[0] = 1` and `(void)vla[0]` are all considered invalid subexpressions in constant expressions), so this seems unintentional. It should probably just be an invalid to flow over the initialization of a VLA during a constant expression in C++23 (and allow `if (false) { int vla[n]; }` and `if !consteval { int vla[n]; }`). --- On a related note, typedefs for vlas are banned even when not used inside `constexpr` functions in C++23: https://godbolt.org/z/1vYo9n5qG ```c++ constexpr bool f(int n) { if !consteval { typedef int vla_t[n]; vla_t vla; } return true; } ``` ``` <source>:3:13: error: variably-modified type 'int[n]' cannot be used in a constexpr function 3 | typedef int vla_t[n]; | ^~~~~~~~~~~~ ``` At least that sort of use should be supported. This is more questionable: ```c++ constexpr bool f(int g()) { typedef int vla_t[g()]; if !consteval { vla_t vla; } return true; } ``` It could get the same treatment as VLA variables (as long as control doesn't flow over it, so it works in `if (false) { typedef int vla_t[g()]; }`), or it could "just work" and evaluate the bounds expression during the constant evaluation and discard the result. The intent of C++23 `constexpr` declaration rules is that every function can be marked `constexpr` even if no constant expression can actually be formed.
shafik commented 2 weeks ago

CC @AaronBallman

https://godbolt.org/z/ahnbbhWeP

AaronBallman commented 2 weeks ago

I agree there's an issue here and we should treat VLAs consistently. However, VLAs are an extension in C++ and that means they should be supported fully within the language as best we can (it's on us to define how that support looks). Given that C++ allows memory allocations so long as they're paired with a deallocation, there's no reason for VLAs to not work in constant expressions. e.g.,

consteval int f(int n) {
  if (n <= 0) return -1;

  int array[n];
  ...
  return array[0];
}

is semantically identical to

consteval int f(int n) {
  if (n <= 0) return -1;

  int *array = new int[n];
  ...
  int ret = array[0];
  delete [] array;
  return ret;
}

While I think it's unfortunate that we enabled VLAs in C++ as an extension by default, it is a conforming extension and it is used in the wild. Further, GCC supports the same extension, but integrates it better with other C++ features: https://godbolt.org/z/rh4hE57Kr