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?

9
  • 1
    It'd be interesting to know why size isn't a static member function.. Commented Oct 6, 2015 at 13:46
  • 1
    I should also add that there's probably some way to get the size via a template, something like: template<typename T, std::size_t N> std::size_t arraysize(const std::array<T, N>& array) { return N; } which could be used above. Still... is this the right way? Seems contorted. Commented Oct 6, 2015 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<char, 8> that doesn't have the same size implementation), but that's clearly not the case here. Commented Oct 6, 2015 at 13:56
  • 1
    Would it be possible to do something like std::tuple_size<decltype(*arr)>::value ? Commented Oct 6, 2015 at 14:06
  • 1
    @DragonRock You'd need to add a remove_reference_t, which makes it quite long. Commented Oct 6, 2015 at 14:10

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

@dyp I could add volatile if you want? ;)
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.

10 Comments

"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: For me, and for cppreference [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.
Here's an example: struct foo { int i; constexpr int bar() { return i; } }; int main() { foo f{rand()}; constexpr auto x = f.bar(); }
@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.
@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).
|
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 ...]
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.