【问题标题】:Inconsistent overload resolution for constexpr member functions across compilers跨编译器的 constexpr 成员函数的重载解决方案不一致
【发布时间】:2019-08-22 02:37:10
【问题描述】:

我遇到了一个只能在 gcc 上重现的编译器错误,我将它缩小到一个最小的可重现样本,该样本在 msvc 上也失败了,但仍然可以用 clang 编译。代码如下:

结构体 { 浮动_x[2]; constexpr float operator[](int index) const { return _x[index]; } float& 运算符[](int index) { return _x[index]; } }; 结构垫 { vec _x[2]; constexpr vec operator[](int index) const { return _x[index]; } vec& operator[](int index) { return _x[index]; } }; constexpr 浮动条(浮动 f) { 返回 f; } constexpr float one(mat const& m) { 返回 m[0][0]; // 在 gcc 5+、msvc 中失败 } constexpr float 2(mat const& m) { 返回条(m[0][0]); // 在 gcc 5+ 中失败 }

据我所知,第 24 行 vec::operator[] 的重载决议不考虑 const 重载(第 5 行),因为 mat::operator[] const(第 13 行)按值返回,而不是按 const参考,但我不确定为什么会阻止考虑 vec::operator[] const。来自 gcc 的错误消息:

:在函数'constexpr float one(const mat&)'中: :24:18: 错误: 调用非 constexpr 函数'float& vec::operator[](int)' 返回 m[0][0]; // 在 gcc 5+、msvc 中失败

来自 msvc:

(22): 错误 C3615: constexpr 函数 'one' 不能产生常量表达式 (24): 注意:失败是由于调用未定义的函数或未声明的 'constexpr' (24): 注意:见'vec::operator []'的用法

原始代码在 msvc 中编译得很好,但示例没有,所以我花了一点时间才找到允许它与 msvc 一起工作的原因。显然,通过另一个 constexpr 函数传递返回值以某种方式迫使 msvc 考虑 const 重载,但我不知道这是什么原因。这是一个错误还是一些深奥的语言规则的结果?哪个编译器是正确的?

这里的最后一个问题是,这只是一个问题,因为 const 重载按值返回,如果它们按 const 引用返回,则任何编译器都没有错误。在这里按值返回是我应该消除的无用悲观吗?

【问题讨论】:

    标签: c++


    【解决方案1】:

    哪个编译器是正确的?

    在这种情况下,两个编译器都是正确的(或者至少不是不正确的)。 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-qualified,然后是float& vec::operator[](int);,因为m[0] 的类型是vec,而不是const-qualified。所以onetwo 调用完全相同的函数,并且两者都是病态的,不需要诊断。

    我不能说为什么 MSVC 对函数 one 给出错误,但对函数 two 却没有。但我不认为它实际上是在使用float vec::operator[](int) const;。我注意到我是否真的尝试在常量表达式中使用two,如

    constexpr mat m = {{{{1,2}}, {{3,4}}}};
    constexpr float val = two(m);
    

    然后MSVC does give somewhat helpful error messages 在那个时候。 MSVC 错过了更早标记问题的机会可以被视为实施质量问题。

    这里按值返回是我应该删除的无用悲观吗?

    在显示的代码中,没有真正的理由不将所有四个operator[] 函数都设为constexpr。不是const 的对象可以在常量表达式中使用,只要它们的初始化发生在同一个常量表达式中,这可以应用于此处涉及的临时vec 对象。 (当然,在更完整的项目中,您可能会在针对此问题简化之前发现问题。)

    【讨论】:

      【解决方案2】:

      这里的最后一个问题是,这只是一个问题,因为 const 重载按值返回,如果它们按 const 引用返回,则任何编译器都没有错误。在这里按值返回是我应该消除的无用悲观吗?

      这个问题始于一个错误的假设。这不仅仅是“一个问题,因为 const 重载按值返回”。相反,问题来自于 mat::operator[] 的 const 重载返回一些 non-const,这导致编译器适当地应用了 vec::operator[] 的非常量重载。

      如果你将mat::operator[]const 重载更改为以下,它仍然会按值返回,但gcc 的警告消失了。

      constexpr const vec operator[](int index) const { return _x[index]; }
      

      这是“constexpr const”不是多余的情况之一。 “constexpr”限定函数,而“const”限定返回类型。

      另一方面,如果你愿意在非 const 版本中通过引用返回,为什么不在 const 版本中通过引用返回呢?有人已经可以得到参考,那么将副本的低效率扔到 const 版本有什么好处呢? (我可能希望反过来看到这一点:非 const 按值返回,因此成员不会被修改,但在 const 版本中按 const 引用返回以提高效率。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-27
        • 2019-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-29
        相关资源
        最近更新 更多