我目前正在使用constexpr数组,并且我注意到我无法使用/std:c++ 17或/std:c++ latest在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范围似乎是一个问题。

最佳答案

复制

我可以使用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);
    }

由于_Idx是无符号的,而_Off是带符号的,而_Off的实际(文字)值为负,因此导致错误C4308。

更简单的测试用例:
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;
}

10-05 20:58
查看更多