在常量表达式中将基类型转换为派生类型时,MSVC 编译失败

MSVC failed to compile when converting a base type to a derived type in a constant-expression

代码:

#include <cstddef>

template <typename value_type, typename iterator_type>
class array_iterator_base
{
protected:
    value_type *ptr;

public:
    constexpr array_iterator_base() : ptr(nullptr) {}
    constexpr iterator_type &operator++()
    {
        ++ptr;
        return *static_cast<iterator_type *>(this); // [1]
    }
};

template <typename value_type>
class array_iterator : public array_iterator_base<value_type, array_iterator<value_type>>
{
public:
    constexpr array_iterator(value_type *ptr)
    {
        this->ptr = ptr;
    }
};

template <typename value_type, std::size_t Size>
class array
{
public:
    using iterator = array_iterator<value_type>;

    value_type m_data[Size];
    constexpr iterator begin() { return iterator(m_data); }
};

class Demo
{
    using storage = array<int, 3>;
    using iterator = typename storage::iterator;

private:
    storage m_arr = { 1, 2, 3 };
    iterator m_iter = m_arr.begin();

public:
    constexpr Demo() {}
    constexpr void field()
    {
        ++m_iter; // MSVC: failed
    }
    constexpr void local_variable()
    {
        storage arr = { 1,2,3 };
        iterator iter = arr.begin();
        ++iter; // MSVC: OK
    }
};

constexpr int ok()
{
    Demo demo;
    demo.local_variable();
    return 1;
}

constexpr int error()
{
    Demo demo;
    demo.field();
    return 1;
}

int main()
{
    constexpr int x = ok();
    // GCC: OK
    // Clang: OK
    // MSVC: OK

    constexpr int y = error(); // [2]
    // GCC: OK
    // Clang: OK
    // MSVC: error
}

[2] 行出错,原因是第 [1] 行:

Expression did not evaluate to a constant.
Failure was caused by cast of object of dynamic type
    array_iterator<value_type>
to type
    iterator_type
with
    [value_type=int]
    [iterator_type=array_iterator<int>]

我正在自己写一个数组class。我决定编写一个数组迭代器 base class,这样任何数组迭代器 class 都可以继承它以节省一些击键。为此,当需要 return 迭代器本身时,我必须将基础 class 转换为派生迭代器 class,因此 return *static_cast<iterator_type *>(this);iterator_type &operator++() 过载。

但是,在 constexpr 上下文中,当迭代器是 class 中的字段时,MSVC 编译失败,但当迭代器是局部变量时,MSVC 编译成功。错误消息说表达式不是常量,因为函数调用涉及转换动态类型(见上文)。

GCC 和 Clang 在两种情况下都编译成功。

有趣的是,在 Visual Studio 中,实际上可以预览 y 的值(通过将光标悬停在其上)就像任何其他 constexpr 变量一样(这让我觉得MSVC 可能是错误的)。

编辑:MSVC 的最新预览版仍然无法编译。

编辑:我已将此错误报告给 Microsoft here

问题:

  1. 根据标准哪个编译器是正确的?

  2. 有没有更好的方法来做我正在做的事情(即为迭代器 class 编写一个基础 class 来继承)?

我看不出有任何理由不能编译。

MSVC 的错误消息没有意义,因为 array_iterator<value_type>value_type = intarray_iterator<int>iterator_type 也是 array_iterator<int>。所以转换是有效的。

this 还引用了在调用 error() 开始的常量表达式求值中创建的对象,因此没有理由拒绝将其作为常量表达式的子求值。

GCC 和 Clang 似乎都同意该分析,因此它可能是一个 MSVC 错误。我也没有看到您在这里使用的 CRTP 有任何问题。不过我看不出有什么好处,因为只有一个 class 定义(array_iterator)继承自 CRTP 基础。

向下转换似乎是合适的。您只需确保绝不会直接创建 array_iterator_base 类型的实例。你可以例如让 array_iterator_base protected 的构造函数来实现这一点。