跨编译器的constexpr成员函数的重载解析不一致

时间:2019-03-31 23:46:11

标签: c++

我遇到了仅在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引用返回,则任何编译器都没有错误。在这里按值返回是否是我应该删除的无用的悲观?

2 个答案:

答案 0 :(得分:2)

  

哪个编译器正确?

在这种情况下,两个编译器都是正确的(或者至少不是正确的)。 C ++标准说([dcl.constexpr]/5),任何不可能在常量表达式中调用的constexpr函数都会使程序“格式错误;无需诊断”。这意味着具有这种情况的程序是不正确的,但是不需要实现(编译器)来打印有关该程序的任何诊断消息。

您正确地认为表达式UIImage首先调用m[0][0],然后调用constexpr vec mat::operator[](int) const;,问题是此float& vec::operator[](int);不是constexpr函数。

  

但是我不确定为什么会阻止考虑vec::operator[]

请注意,任何函数调用(包括可重载的运算符)的重载解析仅使用子表达式的类型和值类别,对于非静态成员函数,则包括隐式class-type参数。特别是在这里,它不考虑表达式是在常量表达式中使用还是在constexpr函数的始终执行的部分中使用。如果两个使用完全相同的代码拼写的表达式涉及根据该表达式的使用方式调用完全不同的函数,那就太令人困惑了。

在这种情况下,vec::operator[] const在常量表达式中不使用时完全有效:

m[0][0]

,在此用法中,由于float runtime_func(const mat& m) { return m[0][0]; } 的类型是const限定的,因此它调用constexpr vec mat::operator[](int) const;,然后由于m的类型是{{1}的情况而调用float& vec::operator[](int);。 },而不是const限定。因此m[0]vec调用完全相同的函数,并且它们都是格式错误的,不需要诊断。

我不能说为什么MSVC会为函数one给出错误,而不会为函数two给出错误。但是我不认为它实际上是在使用one。我注意到我是否实际上尝试在常量表达式中使用two,如

float vec::operator[](int) const;

然后MSVC does give somewhat helpful error messages。 MSVC错过了尽早提出问题的机会,可以认为是实施质量问题。

  

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

在所示的代码中,没有真正的理由不只是使所有四个two函数constexpr mat m = {{{{1,2}}, {{3,4}}}}; constexpr float val = two(m); 。非operator[]的对象可以在常量表达式中使用,只要它们的初始化发生在同一常量表达式中即可,并且这可以应用于此处涉及的临时constexpr对象。 (当然,在将问题简化为更完整的项目后,您可能还需要考虑其他一些问题。)

答案 1 :(得分:1)

  

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

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

如果要将const的{​​{1}}重载更改为以下内容,它仍然会按值返回,但是gcc的警告消失了。

mat::operator[]

这是“ constexpr const vec operator[](int index) const { return _x[index]; } ”不是多余的情况之一。 “ constexpr const”代表函数,而“ constexpr”代表返回的类型。

另一方面,如果您愿意在非const版本中通过引用返回,为什么不在const版本中通过引用返回呢?有人已经可以获得参考,因此将副本的无效性扔到const版本中可以得到什么呢? (我可能希望以另一种方式看到这种情况:为非const按值返回,以便不修改成员,但为提高效率而按const版本中的const引用返回。)