代码之家  ›  专栏  ›  技术社区  ›  chtz

使用非布尔返回值重载相等比较时,是否破坏C++20中的更改或clang trunk/gcc trunk中的回归?

  •  1
  • chtz  · 技术社区  · 4 年前

    // Meta struct describing the result of a comparison
    struct Meta {};
    
    struct Foo {
        Meta operator==(const Foo&) {return Meta{};}
        Meta operator!=(const Foo&) {return Meta{};}
    };
    
    int main()
    {
        Meta res = (Foo{} != Foo{});
    }
    

    https://godbolt.org/z/8GGT78

    主干和主干发出叮当声的错误 -std=c++2a :

    <source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo')
        Meta res = (f != g);
                    ~ ^  ~
    <source>:6:10: note: candidate function
        Meta operator!=(const Foo&) {return Meta{};}
             ^
    <source>:5:10: note: candidate function
        Meta operator==(const Foo&) {return Meta{};}
             ^
    <source>:5:10: note: candidate function (with reversed parameter order)
    

    我知道C++20只允许重载 operator== 编译器将自动生成 operator!= 通过否定 接线员== . 据我所知,这只在返回类型为 bool

    问题的根源在于我们在Eigen中声明了一组算子 == , != < , ... 之间 Array 排列 和标量,它们返回 (然后可以按元素访问或以其他方式使用)。例如。,

    #include <Eigen/Core>
    int main()
    {
      Eigen::ArrayXd a(10);
      a.setRandom();
      return (a != 0.0).any();
    }
    

    与我上面的示例相反,gcc主干甚至会失败: https://godbolt.org/z/RWktKs . 我还没有设法将其简化为一个非本征示例,它在clang trunk和gcc trunk中都失败(顶部的示例非常简化)。

    https://gitlab.com/libeigen/eigen/issues/1833

    我的实际问题是:这实际上是C++20中的一个突破性变化(是否有可能重载比较运算符以返回元对象),还是更可能是clang/gcc中的回归?

    0 回复  |  直到 4 年前
        1
  •  6
  •   T.C. Yksisarvinen    4 年前

    本征问题似乎归结为以下几点:

    using Scalar = double;
    
    template<class Derived>
    struct Base {
        friend inline int operator==(const Scalar&, const Derived&) { return 1; }
        int operator!=(const Scalar&) const;
    };
    
    struct X : Base<X> {};
    
    int main() {
        X{} != 0.0;
    }
    

    1. operator==(const Scalar&, const Derived&)
    2. Base<X>::operator!=(const Scalar&) const

    [over.match.funcs]/4 operator!= 未导入到的范围中 X 使用声明 const Base<X>&

    可能的修复方法:

    • 添加 using Base::operator!=; Derived
    • operator== 接受 const Base& 而不是 const Derived& .
        2
  •  13
  •   Community kfsone    4 年前

    表情 Foo{} != Foo{} 在C++20中有三个候选者(而在C++17中只有一个):

    Meta operator!=(Foo& /*this*/, const Foo&); // #1
    Meta operator==(Foo& /*this*/, const Foo&); // #2
    Meta operator==(const Foo&, Foo& /*this*/); // #3 - which is #2 reversed
    

    这是新的 统治 [over.match.oper]/3.4 . 所有这些候选人都是可行的,因为我们的 Foo const . 为了找到最佳可行的候选人,我们必须通过决胜局。

    最佳可行函数的相关规则如下: [over.match.best]/2 :

    F1 定义为比另一个可行函数更好的函数 F2 如果所有的论点 i ICS i (F1) ICS i (F2) ,然后

    • [... 这个例子中有很多不相关的案例…]或者,如果不是那样,那么

    #2 #3 #3 #1 对于所有的论点 转换序列并不差。

    #2 是重写的候选人,而 #1

    但是两对 #1 #3 #3 #1 / #2 (该参数是 必须接受额外的检查 限定,因此它具有更差的转换顺序)。这 常数 触发器使我们无法选择其中任何一种。

    因此,整个重载解决方案是不明确的。

    据我所知,这只在返回类型为 bool .

    那是不对的。我们无条件地考虑改写和颠倒的候选人。我们的规则是,从 [over.match.oper]/9 :

    operator== 通过运算符的重载解析选择候选对象 @

    也就是说,我们仍然考虑这些候选人。但如果最好的候选人是 就是说,, Meta -结果基本上与该候选人被删除的情况相同。

    希望处于过载解决方案必须考虑返回类型的状态。在任何情况下,这里的代码返回 是无关紧要的-如果它返回,问题也会存在


    谢天谢地,这里的修复很容易:

    struct Foo {
        Meta operator==(const Foo&) const;
        Meta operator!=(const Foo&) const;
        //                         ^^^^^^
    };
    

    常数 ,不再有歧义。所有的参数都是相同的,因此所有的转换序列基本上是相同的。 #1 不是通过重写和 现在会打败 通过不被逆转-这使得 #1 最好的候选人。与我们在C++17中得到的结果相同,只需再多走几步即可实现。

        3
  •  5
  •   Nicol Bolas    4 年前

    [over.match.best]/2列出集合中有效重载的优先级。部分 2.8 F1 F2 其他事项):

    地上二层 一层楼 不是

    operator< 尽管 operator<=>

    [over.match.oper]/3.4.3 告诉我们 operator== 在这种情况下,是一个重写的候选人。

    ,您的运营商忘记了一件至关重要的事情:他们应该 const 使过载解决的早期方面发挥作用。这两个函数都不是完全匹配的,因为它们不是完全匹配的- 常数 常数

    常数 , Clang trunk compiles

    我不能和Eigen的其余部分说话,因为我不知道代码,它非常大,因此不能放在MCVE中。

        4
  •  -1
  •   Ingo Josopait    4 年前

    template<typename T> class gpu_type;
    
    using gpu_bool     = gpu_type<bool>;
    using gpu_int      = gpu_type<int>;
    
    template<typename T>
    class gpu_type
    {
      friend inline gpu_bool operator==(T a, const gpu_type& b);
      friend inline gpu_bool operator!=(T a, const gpu_type& b);
    };
    
    int main()
    {
      gpu_int a;
      gpu_bool b = (a == 0);
    }
    

    提供这些额外的操作员似乎可以解决问题:

    template<typename T>
    class gpu_type
    {
      ...
      friend inline gpu_bool operator==(const gpu_type& b, T a);
      friend inline gpu_bool operator!=(const gpu_type& b, T a);
    };