11

Let's say I have a function like:

int test(std::array<char, 8>* data) {
  char buffer[data->size() * 2];

  [... some code ...]
}

clearly the size of the buffer can be evaluated at compile time: data has a constexpr size of 8 elements, 8 * 2 = 16 bytes.

However, when compiling with -Wall, -pedantic and -std=c++11 I get the infamous error:

warning: variable length arrays are a C99 feature [-Wvla-extension]

which I believe makes sense: array::size() is constexpr, but it is still a method, and in the function above we still have to dereference a pointer, which is not constexpr.

If I try something like:

int test(std::array<char, 8>& data) {
  char buffer[data.size() * 2];
  [...]
}

gcc (tried version 5.2.0) seems happy: there is no warning.

But with clang++ (3.5.1) I still get a warning complaining about variable length arrays.

In my case, I can't easily change the signature of test, it has to take a pointer. So... a few questions:

  1. What is the best / most standard way to get the size of a std::array pointer in constexpr context?

  2. Is the difference in behavior with pointers vs references expected? Which compiler is right about the warning, gcc or clang?

rabexc
  • 571
  • 4
  • 11
  • 1
    It'd be interesting to know why `size` isn't a static member function.. – dyp Oct 06 '15 at 13:46
  • 1
    I should also add that there's probably some way to get the size via a template, something like: `template std::size_t arraysize(const std::array& array) { return N; }` which could be used above. Still... is this the right way? Seems contorted. – rabexc Oct 06 '15 at 13:51
  • I could see it failing in the pointer case but not the reference case since technically, the pointer might be `nullptr`; sure, it can't do anything useful there, but it also means the `size` isn't properly defined. If `size` were virtual, I could see it complaining with both pointers and references (because it might be a type derived from `std::array` that doesn't have the same `size` implementation), but that's clearly not the case here. – ShadowRanger Oct 06 '15 at 13:56
  • Maybe clang complain because reference are implemented through const pointer ? So even with a reference in depth the compiler still have to dereference it ? – Pumkko Oct 06 '15 at 14:03
  • @Pumkko: Your answer was getting some negative comments from inattentive people, but it absolutely did answer "What is the best / most standard way to get the size of a `std::array`?" You could have fixed the "too generic" complaint by specifying `char` and only inferring the size. – Ben Voigt Oct 06 '15 at 14:05
  • 1
    Would it be possible to do something like `std::tuple_size::value` ? – tforgione Oct 06 '15 at 14:06
  • It seems the issue is that the compilers see `&*data` in the case of the pointer or `&data` in the case of the reference, and an lvalue-to-rvalue conversion of it, and report an error because this address cannot be a constant expression. The address is used to initialize the `this` pointer, implicitly via [expr.call]p4. – dyp Oct 06 '15 at 14:09
  • 1
    @DragonRock You'd need to add a `remove_reference_t`, which makes it quite long. – dyp Oct 06 '15 at 14:10
  • Technically it's a warning, not an error, thing you'd get if you strictly required a constant expression as in `constexpr auto s = data.size()`, since it is not a constant expression afaics. – edmz Oct 06 '15 at 14:32

3 Answers3

2

I do not know about 2.

But for 1, we can do this:

template<class T, size_t N>
constexpr std::integral_constant<size_t, N> array_size( std::array<T, N> const& ) {
  return {};
}

then:

void test(std::array<char, 8>* data) {
  using size=decltype(array_size(*data));
  char buffer[size{}];
  (void)buffer;
  // [... some code ...]
}

alternatively:

template<class T, class U, size_t N>
std::array<T,N> same_sized_array( std::array< U, N > const& ) {
  return {};
}

void test(std::array<char, 8>* data) {
  auto buffer = same_sized_array<char>(*data);
  (void)buffer;
  // [... some code ...]
}

finally, a C++14 cleanup:

template<class A>
constexpr const decltype(array_size( std::declval<A>() )) array_size_v = {};

void test3(std::array<char, 8>* data) {
  char buffer[array_size_v<decltype(*data)>];
  (void)buffer;
  // [... some code ...]
}

Live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

The good old C way would be a define, but C++ has const int or for C++11 constexpr. So if you want the compiler to be aware that the size of the array is a compile time constant, the most portable(*) way would be to make it a const or constexpr:

#include <iostream>
#include <array>

const size_t sz = 8;  // constexpr size_t sz for c++11

int test(std::array<char, sz>* data) {
  char buffer[sz * 2];

  buffer[0] = 0;
  return 0;
}
int main()
{
    std::array<char, sz> arr = { { 48,49,50,51,52,53,54,55 } };
    int cr = test(&arr);
    std::cout << cr << std::endl;
    return 0;
}

It compiles without a warning, even with -Wall -pedantic under Clang 3.4.1

For the second question, I cannot imagine why gcc make that difference between pointers and refs here. Either it can determine that size() method on an std::array whose size is a constant is a constant expression - and it should allow both - or it cannot - and it should emit same warning on both. But it does not only concern the compiler, but also the standard library implementation.

The real problem is that pre-C++11 std::array was not part of the standard library, and constexpr is also defined only from C++11 on. So in pre-C++11 mode, both compiler process std::array as an extension, but there is no way for the size method to declare its return value to be a constant expr. This explains why Clang (and gcc facing a pointer) emits the warning.

But if you compile original code in c++11 mode (-std=c++11) you should have no warning, because the standard requires size() method on a std::array to be a constexpr.

(*) The question is about best / most standard ; I cannot say what is best way, and I cannot define most standard either, so I stick to what I would use if I wanted to avoid portability problems on non C++11 compilers.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • *"But if you compile original code in c++11 mode (`-std=c++11`) you should have no warning, because the standard requires `size()` method on a std::array to be a `constexpr`."* A `constexpr` (member) function does not need to produce a constant expression when called. – dyp Oct 06 '15 at 16:04
  • @dyp: For me, and for [cppreference](http://en.cppreference.com/w/cpp/language/constexpr) *[t]he `constexpr` specifier declares that it is possible to evaluate the value of the function or variable at compile time*. So a `constexpr` function with no parameters *is* required do produce something that can be used wherever a litteral constant could be. If it takes parameters, it is required to do so only if its parameters are constant expressions themselves. – Serge Ballesta Oct 06 '15 at 18:35
  • 1
    Here's an example: `struct foo { int i; constexpr int bar() { return i; } }; int main() { foo f{rand()}; constexpr auto x = f.bar(); }` – dyp Oct 06 '15 at 18:47
  • @dyp: when compiling with -std=c++11, clang says : *error: constexpr variable 'x' must be initialized by a constant expression constexpr auto x = f.bar();*. The `constexpr` specifier on `bar` method says that the result will be a constant expression provided the object has been initialized with a constant expression. In `std::array` the size shall be a constant expression so the method `constexpr size_type size() const noexcept;` **is required** to return a constant expression, and a conformant C++11 compiler should **not** issue any warning. – Serge Ballesta Oct 06 '15 at 21:14
  • @dyp: Followup to my first comment: A `constexpr` method is required to return a constant expression if the object was initialized with constant expressions only (or nothing) and if its parameters are constant expressions themselves (or are empty). – Serge Ballesta Oct 06 '15 at 21:21
  • I'm having a hard time finding proof for that in the Standard. However, I agree with the notion: I don't quite understand why the OP is not a constant expression. I can only guess it has to do with the `this` parameter, as I've mentioned in a comment to the OP. – dyp Oct 06 '15 at 21:36
  • Also, consider `constexpr int bar() { return i > 42 ? i : throw i; }` as a member function. It does not produce a constant expression if the object (instance) has been initialized with a constant expression but the value of `i` is smaller or equal to 42. – dyp Oct 06 '15 at 21:37
  • @dyp: your example in not far from the first in *7.1.5 The constexpr specifier [dcl.constexpr] §5* : `constexpr int f(bool b) { return b ? throw 0 : 0; } // OK constexpr int f() { return f(true); } // ill-formed, no diagnostic required` (assuming you meant `constexpr int bar(int i) ...`). Using `bar(21)` as a constant expression would result in an ill-formed program. And as standard says explicitely that no diagnostic would be required we just invoke undefined behaviour :-( – Serge Ballesta Oct 06 '15 at 21:47
  • No, I mean `struct foo { int i; constexpr int bar() { return i > 42 ? i : throw i; } };`. Then, `constexpr foo f0{43}; constexpr auto x = f0.bar();` is well-formed, but `constexpr auto f1{41}; constexpr auto y = f1.bar();` is ill-formed. – dyp Oct 06 '15 at 21:50
  • fwiw, I was using `-std=c++11` in all the experiments I run. I just added that in the original question to clarify it. – rabexc Oct 07 '15 at 03:17
0

What about using std::tuple_size on the decltype of your parameter ?

void test(std::array<char, 8>* data) {
    using data_type = std::remove_pointer<decltype(data)>::type;
    char buffer[std::tuple_size<data_type>::value * 2];
    static_assert(sizeof buffer == 16, "Ouch");
    // [... some code ...]
}
Benoît
  • 3,355
  • 2
  • 29
  • 34