-1

Consider this code:

#include <array>
#include <cstddef>

struct A {
    std::array<std::size_t, 4> test;
    void method() {
        std::size_t duptest[test.size()] = {}; // error: variable-sized object may not be initialized
    }
};

Godbolt Clang

It fails under Clang with the error in the comment. I don't understand why this is considered a VLA, because test.size is a constexpr function. How do I stop duptest from being interpreted as a VLA?

AnArrayOfFunctions
  • 3,452
  • 2
  • 29
  • 66
  • In C++ there is no such term - it's in C and it means Variably Modified type. – AnArrayOfFunctions Apr 27 '22 at 04:34
  • @JaMiT I'm asking why my code doesn't compile on clang – AnArrayOfFunctions Apr 27 '22 at 04:37
  • @JaMiT This shouldn't be any exntesnion in the first place as I see it. – AnArrayOfFunctions Apr 27 '22 at 04:38
  • @273K This should not be a VLA because the method `std::array::size` is `constexpr`. – AnArrayOfFunctions Apr 27 '22 at 04:45
  • [`constexpr`](https://en.cppreference.com/w/cpp/language/constexpr) - *The `constexpr` specifier declares that it is possible to evaluate the value of the function or variable at compile time.* While [`consteval`](https://en.cppreference.com/w/cpp/language/consteval) - *The `consteval` specifier declares a function or function template to be an immediate function, that is, every potentially evaluated call (i.e. call out of an unevaluated context) to the function must (directly or indirectly) produce a compile time constant expression.* – TheScore Apr 27 '22 at 05:12

2 Answers2

3

test means this->test, and evaluating this.size() must first evaluate this->test. But this is not a constant expression so test.size() is not a constant expression, even though std::array::size is constexpr and it doesn't use test. Therefore this is a VLA and is nonstandard.

You might use std::tuple_size.

size_t duptest[std::tuple_size<decltype(test)>()] = {};
HTNW
  • 27,182
  • 1
  • 32
  • 60
  • @AnArrayOfFunctions ...and your code doesn't use a constant expression while my suggestion does. Thus mine works on all conformant compilers. – HTNW Apr 27 '22 at 04:47
  • @HTNW Well I guess you are right but I can't drop my down-vote now - oh well. (I mean for the solution - I'm still certain the clangs fault and gcc right). – AnArrayOfFunctions Apr 27 '22 at 04:51
  • @AnArrayOfFunctions Both Clang and [GCC say](https://godbolt.org/z/7Wz14Kh6b) the same thing?? They both know your code has a VLA. GCC just has better support for VLAs (apparently GCC is okay with initializing them and Clang isn't, go figure) and GCC also doesn't tell you you're doing something out of standard without `-pedantic` (typical...). – HTNW Apr 27 '22 at 04:58
  • @HTNW That's a flaw in the language then. – AnArrayOfFunctions Apr 27 '22 at 05:00
  • Is it really C++'s fault that GCC is a bit of a hot mess when it comes to compliance? I admit that `test.size()` *should* be designed to be a constant expression (maybe just making `size` `static` would work...) but VLAs *are outside C++* and everything weird about this question is really due to flaws in the implementations of VLAs (Clang is weirdly limited, GCC is weirdly secretive). – HTNW Apr 27 '22 at 05:03
  • Just to clarify what (I think) @HTNW is trying to say: `constexpr` gives the compiler *permission* to do compile time evaluation, but doesn't *require* it--and in quite a few cases (including this one) even when it's marked `constexpr`, compile-time evaluation still isn't possible. If you want to guarantee compile-time evaluation, you use `consteval` instead (but `std::array::size()` isn't `consteval`, and even if it was, I'm not sure it would suffice for what's desired here). – Jerry Coffin Apr 27 '22 at 05:42
  • `this` not being constexpr isn't a problem, as long as it's unused by the method. Consider `std::array test; constexpr auto size = test.size();`, which [does compile](https://godbolt.org/z/he6YaPcrE) at function scope. – HolyBlackCat Apr 27 '22 at 17:18
  • @HolyBlackCat ? `this` being not constexpr is exactly the problem. Your example works because your `test.size()` doesn't involve `this` (as in `A`'s `this`). The OP's fails because it does involve that `this`. – HTNW Apr 27 '22 at 18:59
  • @HTNW `.size()` is a non-static member function of `std::array`, so it does involve `this` pointing to that `std::array`. – HolyBlackCat Apr 27 '22 at 19:02
  • @HolyBlackCat Yes, but that `this` is irrelevant. The use of `A`'s `this` is the difference. – HTNW Apr 27 '22 at 19:57
  • And why is that? That's what I'm asking, why is this difference there. May I suggest adding this to the answer? – HolyBlackCat Apr 27 '22 at 20:06
  • @HolyBlackCat Calling `test.size()` requires evaluating `test` (as an lvalue) so that `this` can be bound to it. In the progress of that evaluation, your example doesn't evaluate `A`'s `this` and is allowed, while the original does and so is banned. Since we just checked whether the receiver was a core constant expression, in `size` `this` is an allowed core constant expression too, though `size` probably doesn't use it. This doesn't belong in the answer since the original code fails before `std::array`'s `this` even actually exists. – HTNW Apr 27 '22 at 20:35
  • What's "the receiver"? The address of `test` isn't a core constant, because `test` doesn't have static storage duration. And so is `A`'s `this` (also not a core constant). I'm starting to think that `this` has special treatment (stricter rules). – HolyBlackCat Apr 27 '22 at 20:56
  • The lvalue `test` in your example *is* core constant, and so is its address (the rvalue `&test`), though that's not relevant. By "the receiver" I mean "the lvalue expression syntactically left of `.` whose value will become `*this` in the following method call." `test` is not a *constant expression* (nor is `&test`, still not quite relevant) because the result is not an allowed value for a constant expression. Core constant expressions are much wider than you'd think; that's why there's a difference between "core constant" and just "constant". – HTNW Apr 27 '22 at 21:55
1

The problem is that the expression test.size() is equivalent to this->test.size() but the this object is more of a run-time construct. In particular, in standard C++ the size of an array must be a compile-time constant(aka constant expression) which the expression this->test.size() is not. From expr.const#2:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • this, except in a constexpr function or a constexpr constructor that is being evaluated as part of e;

Now in your example, this appears inside the member function method but the member function method is neither constexpr nor it is being evaluated as part of the expression this->test.size().

Therefore this->test.size() cannot be used to specify the size of the array since that expression is not a compile-time constant.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • 1
    This is not so simple. You can have non-constexpr arguments in a constexpr call, if the function doesn't use them. Both GCC and Clang accept `std::array test; constexpr auto size = test.size();`. – HolyBlackCat Apr 27 '22 at 06:19
  • 1
    Nope, I meant to reply to both answers. If in my example the return value of `.size()` is constexpr despite the array not being constexpr, then I'm unsure why `this->test` not being constexpr ends up being problematic. – HolyBlackCat Apr 27 '22 at 06:33
  • It compiles at function or namespace scope. – HolyBlackCat Apr 27 '22 at 17:07
  • I can't promise I'll always do that, but point taken. *"it is obvious"* Maybe, but it contradicts your answer. `this` for a function-local variable is not constexpr, yet `.size()` called on such a variable ends up being constexpr. – HolyBlackCat Apr 27 '22 at 17:16
  • Yep, I do mean free functions. Am I not seeing something obvious? I'm talking about `this` pointing to the instance of `std::array` inside of its `.size()` method. – HolyBlackCat Apr 27 '22 at 17:22
  • `.size()` is a member function of `std::array`, so it has `this` inside, which isn't `constexpr` in this case, which doesn't stop the return value form being constexpr. – HolyBlackCat Apr 27 '22 at 17:26
  • Yeah, and similarly in `this->test.size()` neither `this` nor the address pf `&test` are needed to compute `.size()`, for the exact reason you stated. I'm unsure why it's so hard to get my point accross. – HolyBlackCat Apr 28 '22 at 17:47
  • It's obvious that there's no `this` in free funtions, but that's not what I'm talking about. I have a feeling that you're not actually reading my comments. :( Let's just drop this matter. – HolyBlackCat Apr 28 '22 at 18:01
  • 1
    @HolyBlackCat I've added an explanation using the standard which explains the observed behavior. You can also use it to find out how in your example of free function the program works. In particular, because in case of free function, if the `this` pointer is used then it is inside a *constexpr* member function `size` and also being evaluated as part of the expression. So it(should, which it does) works in your case. – Jason Apr 29 '22 at 16:11