constexpr环境中std :: array上的std :: prev

时间:2018-08-24 15:24:28

标签: c++ visual-c++ compiler-errors c++17 constexpr

我目前正在使用constexpr数组,并且我注意到我无法使用/ std:c ++ 17或/ std:c ++在MSVC 19.15.26726下获得以下(有效)代码进行编译最新:

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

我从IntelliSense得到的所有忽略突出显示的错误和说std::array的错误是模棱两可的(似乎与同一文件中的某些奇怪的array()函数冲突),我得到以下信息最后一行中的编译器错误:

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 with / std:c ++ 17 works)下的编译器资源管理器中很好地编译(未测试其他版本)。

我很没主意。当我将其放入main方法中时,会编译相同的代码,因此constexpr范围似乎有问题。

1 个答案:

答案 0 :(得分:3)

Repro

我可以使用VS 2017 15.8.1和带有/ std:c ++ 17或/ std:c ++ latest的最新15.9.0 Preview 1.0进行复制。

仅在调试配置中,_ITERATOR_DEBUG_LEVEL不等于0时才会出现问题。

原因

通过MSVC附带的STL代码,我们可以看到_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,可能只是跟进错误。

constexpr上下文中混合使用有符号和无符号类型时,MSVC似乎比GCC更严格。

解决方案可能是MSFT将成员变量_Idx的类型更改为ptrdiff_t。随时提交错误:-)。

解决方法

定义_ITERATOR_DEBUG_LEVEL = 0或替换std::prev()调用:

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