Подтвердить что ты не робот

Size() std:: указатель массива в контексте constexpr

Скажем, у меня есть функция вроде:

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

  [... some code ...]
}

ясно, размер буфера может быть оценен во время компиляции: данные имеют размер constexpr из 8 элементов, 8 * 2 = 16 байт.

Однако при компиляции с -Wall, -pedantic и -std=c++11 я получаю печально известную ошибку:

предупреждение: массивы переменной длины - это функция C99 [-Wvla-extension]

который, как мне кажется, имеет смысл: array::size() is constexpr, но он по-прежнему является методом, и в приведенной выше функции нам еще нужно разыменовать указатель, который не является constexpr.

Если я попробую что-то вроде:

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

gcc (проверенная версия 5.2.0) кажется счастливой: предупреждения нет.

Но с clang++ (3.5.1) я все еще получаю предупреждение, жалующееся на массивы переменной длины.

В моем случае я не могу легко изменить подпись test, он должен взять указатель. Итак... несколько вопросов:

  • Каков наилучший/самый стандартный способ получить размер указателя std::array в контексте constexpr?

  • Является ли разница в поведении с указателями против ожидаемых ссылок? Какой компилятор прав относительно предупреждения, gcc или clang?

4b9b3361

Ответ 1

Я не знаю о 2.

Но для 1 мы можем сделать это:

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

то

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

в качестве альтернативы:

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 ...]
}

наконец, очистка С++ 14:

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 ...]
}

Живой пример.

Ответ 2

Хорошим старым способом C было бы определение, но С++ имеет const int или для С++ 11 constexpr. Поэтому, если вы хотите, чтобы компилятор знал, что размер массива является постоянной времени компиляции, самым портативным (*) способом было бы сделать его const или 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;
}

Он компилируется без предупреждения даже при -Wall -pedantic под Clang 3.4.1

Во втором вопросе я не могу представить, почему gcc делает эту разницу между указателями и ссылками здесь. Либо он может определить, что метод size() на std::array, размер которого является константой, является константным выражением - и он должен позволять как - либо не может - и он должен вызывать одно и то же предупреждение для обоих. Но это касается не только компилятора, но и стандартной реализации библиотеки.

Реальная проблема заключается в том, что pre-С++ 11 std:: array не был частью стандартной библиотеки, а constexpr также определен только с С++ 11 on. Поэтому в режиме pre-С++ 11 оба процесса компилятора std:: array в качестве расширения, но метод size не может объявить его возвращаемое значение константой expr. Это объясняет, почему Clang (и gcc обращается к указателю) выдает предупреждение.

Но если вы скомпилируете исходный код в режиме С++ 11 (-std=c++11), у вас не должно быть предупреждения, потому что для стандартного метода size() для std::array должен быть constexpr.

(*) Вопрос о лучшем/наиболее стандартном; Я не могу сказать, что лучше всего, и я не могу определить большинство стандартных, поэтому я придерживаюсь того, что я буду использовать, если бы хотел избежать проблем с переносимостью на компиляторах, отличных от С++ 11.

Ответ 3

Как насчет использования std::tuple_size в объявлении вашего параметра?

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 ...]
}