在常量表达式中将基类型转换为派生类型时,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。
问题:
根据标准哪个编译器是正确的?
有没有更好的方法来做我正在做的事情(即为迭代器 class 编写一个基础 class 来继承)?
我看不出有任何理由不能编译。
MSVC 的错误消息没有意义,因为 array_iterator<value_type>
和 value_type = int
是 array_iterator<int>
而 iterator_type
也是 array_iterator<int>
。所以转换是有效的。
this
还引用了在调用 error()
开始的常量表达式求值中创建的对象,因此没有理由拒绝将其作为常量表达式的子求值。
GCC 和 Clang 似乎都同意该分析,因此它可能是一个 MSVC 错误。我也没有看到您在这里使用的 CRTP 有任何问题。不过我看不出有什么好处,因为只有一个 class 定义(array_iterator
)继承自 CRTP 基础。
向下转换似乎是合适的。您只需确保绝不会直接创建 array_iterator_base
类型的实例。你可以例如让 array_iterator_base
protected
的构造函数来实现这一点。
代码:
#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。
问题:
根据标准哪个编译器是正确的?
有没有更好的方法来做我正在做的事情(即为迭代器 class 编写一个基础 class 来继承)?
我看不出有任何理由不能编译。
MSVC 的错误消息没有意义,因为 array_iterator<value_type>
和 value_type = int
是 array_iterator<int>
而 iterator_type
也是 array_iterator<int>
。所以转换是有效的。
this
还引用了在调用 error()
开始的常量表达式求值中创建的对象,因此没有理由拒绝将其作为常量表达式的子求值。
GCC 和 Clang 似乎都同意该分析,因此它可能是一个 MSVC 错误。我也没有看到您在这里使用的 CRTP 有任何问题。不过我看不出有什么好处,因为只有一个 class 定义(array_iterator
)继承自 CRTP 基础。
向下转换似乎是合适的。您只需确保绝不会直接创建 array_iterator_base
类型的实例。你可以例如让 array_iterator_base
protected
的构造函数来实现这一点。