不能在 Catch 测试中使用重载的比较运算符

Can't use overloaded comparison operator with Catch test

我使用 Catch 2.11.1 进行了简单的单元测试:

#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include <utility>
#include <any>

namespace A::B
{
    namespace C
    {
        struct S
        {
        };
    }

    using type = std::pair<C::S, std::any>;
}

inline bool operator==(A::B::type const&, A::B::type const&)
{
    return true;
}

TEST_CASE("test", "[test]")
{
    auto t1 = std::make_pair(A::B::C::S(), std::any());
    auto t2 = std::make_pair(A::B::C::S(), std::any());

    REQUIRE(t1 == t2);
}

上面的简单程序会产生以下错误:

$ g++ -Wall -Wextra -Wpedantic test-single.cpp -std=c++17
In file included from /usr/include/c++/9/bits/stl_algobase.h:64,
                 from /usr/include/c++/9/bits/char_traits.h:39,
                 from /usr/include/c++/9/string:40,
                 from catch.hpp:457,
                 from test-single.cpp:2:
/usr/include/c++/9/bits/stl_pair.h: In instantiation of ‘constexpr bool std::operator==(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&) [with _T1 = A::B::C::S; _T2 = std::any]’:
catch.hpp:2289:98:   required from ‘bool Catch::compareEqual(const LhsT&, const RhsT&) [with LhsT = std::pair<A::B::C::S, std::any>; RhsT = std::pair<A::B::C::S, std::any>]’
catch.hpp:2318:34:   required from ‘const Catch::BinaryExpr<LhsT, const RhsT&> Catch::ExprLhs<LhsT>::operator==(const RhsT&) [with RhsT = std::pair<A::B::C::S, std::any>; LhsT = const std::pair<A::B::C::S, std::any>&]’
test-single.cpp:28:5:   required from here
/usr/include/c++/9/bits/stl_pair.h:449:24: error: no match for ‘operator==’ (operand types are ‘const A::B::C::S’ and ‘const A::B::C::S’)
  449 |     { return __x.first == __y.first && __x.second == __y.second; }
      |              ~~~~~~~~~~^~~~~~~~~~~~

[此后还有更多消息...]

错误消息的关键部分是这一行:

/usr/include/c++/9/bits/stl_pair.h: In instantiation of ‘constexpr bool std::operator==(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&) [with _T1 = A::B::C::S; _T2 = std::any]’:

从错误消息中可以清楚地看出,调用的是 std::pair 的标准 std::operator== 函数,而不是我重载的 operator== 函数。

如果我在 Catch REQUIRE 宏中进行比较,那么它会起作用:

auto result = t1 == t2;  // Invokes my overloaded comparison operator
REQUIRE(result);

这是 Catch 的问题,还是我的运算符函数的问题?


注意:我正在使用最新版本的 GCC 9.2 在 Debian SID 上构建

$ g++ --version
g++ (Debian 9.2.1-23) 9.2.1 20200110
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

扩展操作数以提供良好诊断输出的魔法有时会失败。

解决方法是用一些括号禁用它:

REQUIRE((t1 == t2));

这实际上是与变量相同的解决方法。

文档mentions this problem in the context of more complex expressions。我不确定在你的情况下触发这种情况的确切原因,但从堆栈跟踪中注意到你的 operator== 实际上并没有被调用,而是 Catch::BinaryExpr::operator==Catch::compareEqual,这似乎无法访问(或选择不使用)您的实施。无论哪种方式,解决方案都是如上所述禁用分解机制。

请注意,即使使用 Lightness 建议的括号,您显示的代码也非常脆弱。

我猜你原来是在 ADL-only 领域,因为宏内部的依赖名称查找(参见 https://en.cppreference.com/w/cpp/language/adl 的最后注释),而且你的代码显然不是 ADL 可行的。添加括号使整个事情只是一个不合格的查找,而不是仅 ADL(再次猜测)。在这种情况下,不合格查找的非 ADL 部分可以节省您的时间,但它会因完全不相关的代码更改而分崩离析。

考虑此代码而不是 TEST_CASE,使用括号大概可以归结为:

namespace test
{
    bool foo()
    {
        auto t1 = std::make_pair(A::B::C::S(), std::any());
        auto t2 = std::make_pair(A::B::C::S(), std::any());

        return t1 == t2;
    }
}

这会按预期编译和工作:https://godbolt.org/z/HiuWWy

现在在全局 operator==t1 == t2 之间添加一个完全不相关的 operator==:

namespace test
{
    struct X{};
    bool operator==(X, X);

    bool foo()
    {
        auto t1 = std::make_pair(A::B::C::S(), std::any());
        auto t2 = std::make_pair(A::B::C::S(), std::any());

        return t1 == t2;
    }
}

您数不胜数:https://godbolt.org/z/BUQC9Y

找不到全局命名空间中的 operator==,因为(的非 ADL 部分)非限定名称查找在具有 first 的封闭范围内停止=52=]任何 operator==。由于找不到任何有用的东西,它退回到使用内置的 std::pair 比较运算符(通过 ADL 找到),这是行不通的。

只需将运算符重载放在它们所操作的对象的名称空间中即可。并且根据推论,不要为来自 std(或您不允许接触的其他名称空间)的设施重载运算符。


从评论中添加:

标准目前还说考虑了模板参数的命名空间,所以将 operator== 放在 namespace C 中是可行的(因为 std::pair 的第一个模板参数来自那里): https://godbolt.org/z/eV8Joj

但是,1. 这与您的类型别名不太吻合,并且 2. 有一些运动可以使 ADL 不那么疯狂,我已经看到关于摆脱 "consider namespaces of template parameters" 的讨论。见 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0934r0.pdf:

Why on earth would we look into the namespaces of template arguments? Nothing in there could possibly be part of the interface of the type, unless the template arguments were also base classes or something. -- Herb Sutter

我不知道这篇论文今天的立场,但我会避免在新代码中依赖这种 ADL。