Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::prev on std::array in a constexpr environment

I'm currently playing around with constexpr arrays, and i noticed that i can't get the following (valid) code to compile under MSVC 19.15.26726 with /std:c++17 or /std:c++latest:

#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());

Ignoring all the highlighting errors and the error that says std::array is ambigous (seems to conflict with some weird array() function in the same file) that I'm getting from IntelliSense, I get the following compiler errors in the last line:

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

It compiles fine in compiler explorer under gcc (x86-64 gcc (trunk)) and MSVC (x86-64 edit: MSVC Pre 2018 with /std:c++17 works) (didn't test the others).

I'm seriously out of ideas. The same code compiles when i put it in a main method, so it seems to be an issue with the constexpr scope.

like image 567
Julian Wiesler Avatar asked Apr 24 '26 16:04

Julian Wiesler


1 Answers

Repro

I can reproduce with VS 2017 15.8.1 and also with the latest 15.9.0 Preview 1.0 with /std:c++17 or /std:c++latest.

The problem only occurs if _ITERATOR_DEBUG_LEVEL does not equal 0, typically in debug configurations.

Cause

Spelunking through the STL code coming with MSVC, we can see that _Array_const_iterator has two different implementations depending on _ITERATOR_DEBUG_LEVEL. If _ITERATOR_DEBUG_LEVEL does not equal 0, the iterator stores a base pointer to the array and an index variable _Idx of type std::size_t. Otherwise it stores just a pointer.

Part of the problem is caused by _Array_const_iterator::operator+=(), which is indirectly called by std::prev() with argument value of -1:

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

Error C4308 is caused because _Idx is unsigned, whereas _Off is signed and the actual (literal) value of _Off is negative.

Even simpler test case:

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

The assignment to test1 produces error C4308 and warning C4307 too. I'm not sure about C4146, it's propably just a follow-up error.

MSVC seems to be more strict than GCC when signed and unsigned types are mixed in a constexpr context.

The solution would propably be for MSFT to change the type of member variable _Idx to ptrdiff_t. Feel free to file a bug :-).

Workaround

Define _ITERATOR_DEBUG_LEVEL = 0 or replace the std::prev() call:

constexpr iterator_type getIteratorBefore(iterator_type it) {
    return --it;
}
like image 55
zett42 Avatar answered Apr 28 '26 14:04

zett42