std :: prev для std :: array в среде constexpr - PullRequest
0 голосов
/ 24 августа 2018

В настоящее время я работаю с массивами constexpr и заметил, что не могу получить следующий (действительный) код для компиляции под MSVC 19.15.26726 с / std: c ++ 17 или / std: c ++ последнее:

#include <array>
using array_type = std::array<unsigned int, 3>;
using iterator_type = array_type::const_iterator;
constexpr array_type arr{ { 1,2,3 } };

constexpr iterator_type getIteratorBefore(iterator_type it) {
    return std::prev(it);
}

constexpr iterator_type test = getIteratorBefore(arr.end());

Игнорирование всех ошибок выделения и ошибки, которая говорит, что std::array является неоднозначным (кажется, конфликтует с какой-то странной array() функцией в том же файле), которую я получаю от IntelliSense, я получаю следующие ошибки компилятора в последняя строка:

error C4146: unary minus operator applied to unsigned type, result still unsigned
error C4308: negative integral constant converted to unsigned type
warning C4307: '+': integral constant overflow

Прекрасно компилируется в проводнике компилятора в gcc (x86-64 gcc (trunk)) и MSVC (x86-64 edit: MSVC Pre 2018 с / std: c ++ 17 работает) (остальные не тестировал).

У меня серьезно нет идей. Тот же код компилируется, когда я помещаю его в метод main, поэтому, похоже, проблема в области действия constexpr.

1 Ответ

0 голосов
/ 25 августа 2018

Репро

Я могу воспроизвести с VS 2017 15.8.1, а также с последней версией 15.9.0 Preview 1.0 с / std: c ++ 17 или / std: c ++ последней.

Проблема возникает, только если _ITERATOR_DEBUG_LEVEL не равно 0, как правило, в конфигурациях отладки.

Причина

Рассматривая код STL, поставляемый с MSVC, мы видим, что _Array_const_iterator имеет две разные реализации в зависимости от _ITERATOR_DEBUG_LEVEL. Если _ITERATOR_DEBUG_LEVEL не равно 0, итератор сохраняет базовый указатель на массив и индексную переменную _Idx типа std::size_t. В противном случае хранится только указатель.

Часть проблемы вызвана _Array_const_iterator::operator+=(), который косвенно вызывается std::prev() со значением аргумента -1:

_CONSTEXPR17 _Array_const_iterator& operator+=(const ptrdiff_t _Off)
    {   // increment by integer
    _Verify_offset(_Off);
    _Idx += _Off;          //<-- error C4308
    return (*this);
    }

Ошибка C4308 вызвана тем, что _Idx не подписано, тогда как _Off подписано, а фактическое (буквальное) значение _Off отрицательно.

Еще более простой тестовый пример:

constexpr unsigned Test(unsigned x, int d) { x += d; return x; }
constexpr auto test1 = Test( 5, -1 );   //<-- error C4308
constexpr auto test2 = Test( 5, 1 );    //<-- OK

Присвоение test1 приводит к ошибке C4308 и предупреждению C4307. Я не уверен насчет C4146, возможно, это просто ошибка повторения.

MSVC выглядит более строгим, чем GCC, когда типы со знаком и без знака смешиваются в контексте constexpr.

Возможно, MSFT предложит изменить тип переменной-члена _Idx на ptrdiff_t. Не стесняйтесь подавать ошибку: -).

Обход

Определите _ITERATOR_DEBUG_LEVEL = 0 или замените std::prev() вызов:

constexpr iterator_type getIteratorBefore(iterator_type it) {
    return --it;
}
...