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.
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.
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 :-).
Define _ITERATOR_DEBUG_LEVEL = 0 or replace the std::prev() call:
constexpr iterator_type getIteratorBefore(iterator_type it) {
return --it;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With