![](/img/trans.png)
[英]constexpr array member with template specialization: inconsistent behavior cross compilers
[英]Inconsistent overload resolution for constexpr member functions across compilers
我遇到了一个只在gcc上重现的编译器错误,我把它缩小到一个最小的可重复样本,它也在msvc上失败但仍然可以用clang编译好。 这是代码:
struct vec { float _x[2]; constexpr float operator[](int index) const { return _x[index]; } float& operator[](int index) { return _x[index]; } }; struct mat { vec _x[2]; constexpr vec operator[](int index) const { return _x[index]; } vec& operator[](int index) { return _x[index]; } }; constexpr float bar(float f) { return f; } constexpr float one(mat const& m) { return m[0][0]; // fails in gcc 5+, msvc } constexpr float two(mat const& m) { return bar(m[0][0]); // fails in gcc 5+ }
据我所知,第24行的vec :: operator []的重载解析不考虑const重载(第5行),因为mat :: operator [] const(第13行)按值返回,而不是通过const引用返回,但是我不确定为什么会阻止考虑vec :: operator [] const。 来自gcc的错误消息:
: In function 'constexpr float one(const mat&)': :24:18: error: call to non-constexpr function 'float& vec::operator[](int)' return m[0][0]; // fails in gcc 5+, msvc
从msvc:
(22): error C3615: constexpr function 'one' cannot result in a constant expression (24): note: failure was caused by call of undefined function or one not declared 'constexpr' (24): note: see usage of 'vec::operator []'
原始代码在msvc中编译得很好,但样本没有,所以我花了一点时间才找到允许它与msvc一起工作的东西。 显然,通过另一个constexpr函数传递返回值会以某种方式迫使msvc考虑const重载,但我不知道这是什么原因。 这是一个错误还是一些深奥的语言规则的结果? 哪个编译器正确?
这里的最后一个问题是,这只是一个问题,因为const重载按值返回,如果它们通过const引用返回,则任何编译器都没有错误。 在这里返回值是一个无用的悲观,我应该删除?
哪个编译器正确?
在这种情况下,两个编译器都是正确的(或者至少是不正确的)。 C ++标准说( [dcl.constexpr] / 5 )任何constexpr函数都不能在常量表达式中调用,这使得程序“形成错误;无需诊断”。 这意味着具有该条件的程序不正确,但不需要实现(编译器)来打印关于它的任何诊断消息。
你正确地认为表达式m[0][0]
首先调用constexpr vec mat::operator[](int) const;
然后调用float& vec::operator[](int);
,问题是这个vec::operator[]
不是constexpr函数。
但我不确定为什么会阻止考虑
vec::operator[] const
。
请注意,任何函数调用(包括可重载运算符)的重载解析仅使用子表达式的类型和值类别,包括非静态成员函数的隐式类型参数。 特别是在这里,它不考虑表达式是在常量表达式中使用还是在constexpr函数的始终执行的片段中使用。 如果用完全相同的代码拼写的两个表达式涉及根据如何使用该表达式调用完全不同的函数,那将太令人困惑。
在这种情况下, m[0][0]
在常量表达式中不使用时完全有效:
float runtime_func(const mat& m) { return m[0][0]; }
在那个用途中,它调用constexpr vec mat::operator[](int) const;
因为m
的类型是const限定的,然后是float& vec::operator[](int);
因为m[0]
的类型是vec
,而不是const限定的。 因此, one
和two
调用完全相同的功能,两者都是格式错误,无需诊断。
我不能说,为什么MSVC给出了一个错误的功能, one
而不是功能two
。 但我不认为它实际上是使用float vec::operator[](int) const;
代替。 我注意到我是否真的尝试在常量表达式中使用two
,如
constexpr mat m = {{{{1,2}}, {{3,4}}}};
constexpr float val = two(m);
然后MSVC确实在那时提供了一些有用的错误消息 。 MSVC错过了提前标记该问题的机会,可被视为实施质量问题。
在这里返回值是一个无用的悲观,我应该删除?
在显示的代码中,没有真正的理由不仅仅使所有四个operator[]
函数constexpr
。 非const
对象可以在常量表达式中使用,只要它们的初始化发生在同一个常量表达式中,并且这可以应用于此处涉及的临时vec
对象。 (当然,在为这个问题简化它之前,你在发现问题的更完整的项目中可能会考虑其他一些事情。)
这里的最后一个问题是,这只是一个问题,因为const重载按值返回,如果它们通过const引用返回,则任何编译器都没有错误。 在这里返回值是一个无用的悲观,我应该删除?
这个问题始于错误的假设。 这不是“唯一的问题,因为const重载按值返回”。 相反,问题来自mat::operator[]
的const重载,返回非const的东西,这导致编译器适当地应用vec::operator[]
的非const重载。
如果你要将mat::operator[]
的const
重载更改为以下内容,它仍会按值返回,但gcc的警告会消失。
constexpr const vec operator[](int index) const { return _x[index]; }
这是“ constexpr const
”不冗余的情况之一。 “ constexpr
”符合函数的要求,而“ const
”则限定返回的类型。
另一方面,如果您愿意在非const版本中通过引用返回,为什么不在const版本中通过引用返回? 有人已经可以获得参考,那么通过将副本的低效率抛入const版本可以获得什么? (我可能期望看到另一种方式:非const返回值,因此不修改成员,但在const版本中返回const引用以提高效率。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.