当前位置: 首页 > 知识库问答 >
问题:

C++20使用等式运算符破坏现有代码的行为?

鱼锦
2023-03-14

我在调试这个问题的时候碰到了这个。

我把它删减到只使用Boost运算符:

>

  • 编译器资源管理器C++17 C++20

    #include <boost/operators.hpp>
    
    struct F : boost::totally_ordered1<F, boost::totally_ordered2<F, int>> {
        /*implicit*/ F(int t_) : t(t_) {}
        bool operator==(F const& o) const { return t == o.t; }
        bool operator< (F const& o) const { return t <  o.t; }
      private: int t;
    };
    
    int main() {
        #pragma GCC diagnostic ignored "-Wunused"
        F { 42 } == F{ 42 }; // OKAY
        42 == F{42};         // C++17 OK, C++20 infinite recursion
        F { 42 } == 42;      // C++17 OK, C++20 infinite recursion
    }
    

    这个程序可以在GCC和CLANG中用C++17(启用了UBSAN/ASAN)编译并运行良好。

    当您将隐式构造函数更改为explicit时,有问题的行显然不再在C++17上编译

    令人惊讶的是,这两个版本都在C++20(v1和v2)上编译,但它们导致了在C++17上无法编译的两行上的无限递归(崩溃或紧密循环,取决于优化级别)。

    显然,这种通过升级到C++20而潜入的无声bug是令人担忧的。

    问题:

    • 此C++20行为是否符合(我希望如此)
    • 到底什么是干扰?我怀疑这可能是由于C++20新的“宇宙飞船操作符”的支持,但不明白它是如何改变这段代码的行为的。
  • 共有1个答案

    呼延运恒
    2023-03-14

    的确,C++20不幸地使这段代码无限递归。

    下面是一个简化的示例:

    struct F {
        /*implicit*/ F(int t_) : t(t_) {}
    
        // member: #1
        bool operator==(F const& o) const { return t == o.t; }
    
        // non-member: #2
        friend bool operator==(const int& y, const F& x) { return x == y; }
    
    private:
        int t;
    };
    

    让我们看看42==F{42}

    在C++17中,我们只有一个候选者:非成员候选者(#2),所以我们选择它。其主体x==y本身只有一个候选项:成员候选项(#1),它涉及将y隐式转换为f。然后,成员候选比较两个整数成员,这是完全好的。

    在C++20中,初始表达式42==F{42}现在有两个候选项:一个是以前的非成员候选项(#2),另一个是反向成员候选项(#1反向)。#2是更好的匹配--我们精确匹配两个参数,而不是调用一个转换,因此选择了它。

    但是,x==y现在有两个候选项:成员候选项(#1),还有反向的非成员候选项(#2反向)。#2是更好的匹配,原因与之前的匹配相同:不需要转换。因此我们改为计算y==x。无限递归。

    非反转的候选人优先于反转的候选人,但仅作为抢七者。更好的转换顺序总是第一。

    好吧太好了,我们怎么解决它?最简单的选择是完全删除非成员候选人:

    struct F {
        /*implicit*/ F(int t_) : t(t_) {}
    
        bool operator==(F const& o) const { return t == o.t; }
    
    private:
        int t;
    };
    

    42==F{42}在这里的计算结果为F{42}。operator==(42),可以正常工作。

    如果我们想要保留非成员候选人,我们可以显式地添加它的反向候选人:

    struct F {
        /*implicit*/ F(int t_) : t(t_) {}
        bool operator==(F const& o) const { return t == o.t; }
        bool operator==(int i) const { return t == i; }
        friend bool operator==(const int& y, const F& x) { return x == y; }
    
    private:
        int t;
    };
    

    这使得42==F{42}仍然选择非成员候选者,但现在在正文中的x==y将选择成员候选者,这将执行正常的相等。

    最后一个版本还可以删除非成员候选。以下方法也适用于所有测试用例,而不需要递归(这也是我接下来在C++20中编写比较的方法):

    struct F {
        /*implicit*/ F(int t_) : t(t_) {}
        bool operator==(F const& o) const { return t == o.t; }
        bool operator==(int i) const { return t == i; }
    
    private:
        int t;
    };
    
     类似资料:
    • 我对这个结果很困惑。 是不同的对象,当处理时,它会比较第一个元素的地址,所以 的行为。 (*)是否等于 ? 是一个对象吗?和都指向这个物体的第一个元素?

    • 这个问题与现有的问题“使用C11的‘自动’能提高性能吗?” 这个问题的一个答案表明,使用不仅会有积极的影响,也会有消极的影响。 我认为我们需要一个单独的问题,答案集中在自动的那一面。

    • 问题内容: 来自Python,我不习惯看到超过80列的代码行。所以当我遇到这个: 我试图打破它 但是我明白了 我还尝试过按回车键并在末尾加分号来打破界限: 但是我再次得到: 所以我想知道用什么语言来做到这一点? 问题答案: 首先介绍一些背景。Go的正式语法在许多产品中都使用分号作为终止符,但是Go程序可能会省略大多数(它们应该有一个更清晰易读的源;也可以删除不必要的分号)。 该规范列出了确切的规则

    • GCC6有一个新的优化器特性:它假设总是不为空,并基于此进行优化。 值范围传播现在假定C++成员函数的this指针是非空的。这消除了常见的空指针检查,但也破坏了一些不一致的代码基(如Qt-5、Chromium、KDevelop)。作为临时解决办法,可以使用-fno-delete-null-pointer-checks。使用-fsanitize=undefined可以识别错误的代码。 变更文档明确指

    • 问题内容: 以下是一个名为AT5G60410.gff的大文件的示例: 我在使用grep从中提取特定行时遇到了一些麻烦。我想提取所有在第三列中指定的“基因”或“外显子”类型的行。当这不起作用时,我感到很惊讶: 没有结果返回。我哪里出问题了? 问题答案: 您需要逃脱。以下应做的工作。

    • 本文向大家介绍C# 格式化字符串的实现代码,包括了C# 格式化字符串的实现代码的使用技巧和注意事项,需要的朋友参考一下 1 前言    如果你熟悉Microsoft Foundation Classes(MFC)的CString,Windows Template Library(WTL)的CString或者Standard Template Library(STL)的字符串类,那么你对String