size() указателя std::array в контексте 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() это constexpr, но это все еще метод, и в приведенной выше функции мы все еще должны разыменовывать указатель, который не является constexpr.

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

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

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

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

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

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

  2. Ожидается ли разница в поведении указателей и ссылок? Какой компилятор прав в предупреждении, gcc или clang?


person rabexc    schedule 06.10.2015    source источник
comment
Было бы интересно узнать, почему size не является статической функцией-членом.   -  person dyp    schedule 06.10.2015
comment
Я также должен добавить, что, вероятно, есть способ получить размер через шаблон, что-то вроде: template<typename T, std::size_t N> std::size_t arraysize(const std::array<T, N>& array) { return N; }, который можно использовать выше. Тем не менее... это правильный путь? Кажется искаженным.   -  person rabexc    schedule 06.10.2015
comment
Я мог видеть сбой в случае указателя, но не в случае ссылки, поскольку технически указатель может быть nullptr; конечно, он не может сделать там ничего полезного, но это также означает, что size не определено должным образом. Если бы size был виртуальным, я бы увидел, что он ругается как с указателями, так и со ссылками (потому что это может быть тип, производный от std::array<char, 8>, который не имеет такой же реализации size), но здесь это явно не тот случай.   -  person ShadowRanger    schedule 06.10.2015
comment
Может быть, clang жалуется, потому что ссылка реализована через указатель const? Таким образом, даже при наличии глубокой ссылки компилятор все равно должен ее разыменовать?   -  person Pumkko    schedule 06.10.2015
comment
@Pumkko: Ваш ответ получил несколько негативных комментариев от невнимательных людей, но он абсолютно точно ответил. Каков наилучший / самый стандартный способ получить размер std::array? Вы могли бы исправить слишком общую жалобу, указав char и указав только размер.   -  person Ben Voigt    schedule 06.10.2015
comment
Можно ли сделать что-то вроде std::tuple_size<decltype(*arr)>::value ?   -  person tforgione    schedule 06.10.2015
comment
Кажется, проблема в том, что компиляторы видят &*data в случае указателя или &data в случае ссылки и его преобразование из lvalue в rvalue и сообщают об ошибке, поскольку этот адрес не может быть постоянным выражением. Адрес используется для инициализации указателя this неявно через [expr.call]p4.   -  person dyp    schedule 06.10.2015
comment
@DragonRock Вам нужно добавить remove_reference_t, что делает его довольно длинным.   -  person dyp    schedule 06.10.2015
comment
Технически это предупреждение, а не ошибка, которую вы получили бы, если бы вам строго требовалось постоянное выражение, как в constexpr auto s = data.size(), поскольку это не является постоянным выражением.   -  person edmz    schedule 06.10.2015


Ответы (3)


про 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 ...]
}

наконец, очистка C++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 ...]
}

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

person Yakk - Adam Nevraumont    schedule 06.10.2015
comment
@dyp Я могу добавить volatile, если хочешь? ;) - person Yakk - Adam Nevraumont; 06.10.2015

Старый добрый способ C был бы определением, но C++ имеет const int или для C++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, размер которого является константой, является константным выражением, и он должен разрешать оба, либо не может, и он должен выдавать одно и то же предупреждение для обоих. Но это касается не только компилятора, но и реализации стандартной библиотеки.

Настоящая проблема заключается в том, что std::array до C++11 не был частью стандартной библиотеки, а constexpr также определяется только начиная с C++11. Таким образом, в режиме до C++11 оба компилятора обрабатывают std::array как расширение, но метод size не может объявить возвращаемое значение постоянным expr. Это объясняет, почему Clang (и gcc, обращенный к указателю) выдает предупреждение.

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

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

person Serge Ballesta    schedule 06.10.2015
comment
Но если вы компилируете исходный код в режиме c++11 (-std=c++11), у вас не должно быть предупреждений, потому что стандарт требует, чтобы метод size() в std::array был constexpr. constexpr (член) функция не должна создавать константное выражение при вызове. - person dyp; 06.10.2015
comment
@dyp: Для меня и для cppreference [t]он Спецификатор constexpr объявляет, что можно оценить значение функции или переменной во время компиляции. Таким образом, функция constexpr без параметров является обязательной для создания чего-то, что можно использовать везде, где может быть литеральная константа. Если он принимает параметры, это необходимо делать только в том случае, если его параметры сами являются константными выражениями. - person Serge Ballesta; 06.10.2015
comment
Вот пример: struct foo { int i; constexpr int bar() { return i; } }; int main() { foo f{rand()}; constexpr auto x = f.bar(); } - person dyp; 06.10.2015
comment
@dyp: при компиляции с -std=c++11 clang говорит: ошибка: переменная constexpr 'x' должна быть инициализирована константным выражением constexpr auto x = f.bar();. Спецификатор constexpr в методе bar говорит, что результатом будет константное выражение при условии, что объект был инициализирован константным выражением. В std::array размер должен быть константным выражением, поэтому метод constexpr size_type size() const noexcept; требуется для возврата константного выражения, а совместимый компилятор C++11 не должен не выдавать никаких предупреждений. - person Serge Ballesta; 07.10.2015
comment
@dyp: продолжение моего первого комментария: метод constexpr требуется для возврата константного выражения, если объект был инициализирован только константными выражениями (или ничем) и если его параметры сами являются константными выражениями (или пусты). - person Serge Ballesta; 07.10.2015
comment
Мне трудно найти подтверждение этому в Стандарте. Однако я согласен с этим мнением: я не совсем понимаю, почему ОП не является постоянным выражением. Я могу только догадываться, что это связано с параметром this, как я уже упоминал в комментарии к OP. - person dyp; 07.10.2015
comment
Также рассмотрите constexpr int bar() { return i > 42 ? i : throw i; } как функцию-член. Он не создает константное выражение, если объект (экземпляр) был инициализирован константным выражением, но значение i меньше или равно 42. - person dyp; 07.10.2015
comment
@dyp: ваш пример недалеко от первого в 7.1.5 Спецификатор constexpr [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 (при условии, что вы имели в виду constexpr int bar(int i) ...). Использование bar(21) в качестве константного выражения привело бы к некорректной программе. И, поскольку стандарт прямо говорит, что никакой диагностики не требуется, мы просто вызываем неопределенное поведение :-( - person Serge Ballesta; 07.10.2015
comment
Нет, я имею в виду struct foo { int i; constexpr int bar() { return i > 42 ? i : throw i; } };. Тогда constexpr foo f0{43}; constexpr auto x = f0.bar(); правильно сформировано, но constexpr auto f1{41}; constexpr auto y = f1.bar(); неправильно сформировано. - person dyp; 07.10.2015
comment
Между прочим, я использовал -std=c++11 во всех своих экспериментах. Я просто добавил это в исходный вопрос, чтобы уточнить его. - person rabexc; 07.10.2015

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

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 ...]
}
person Benoît    schedule 07.10.2015