6

我有一个使用 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::operator==函数,而不是我的重载函数。std::pairoperator==

如果我不在CatchREQUIRE宏中进行比较,那么它可以工作:

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.
4

2 回答 2

7

请注意,即使使用 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==现在在你的全局operator==和之间添加一个完全不相关的t1 == t2

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 部分)在具有any的第一个封闭范围内停止。由于这没有发现任何有用的东西,因此它回退到使用内置的比较运算符(通过 ADL 找到),这将不起作用。 operator==std::pair

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


从评论中添加:

该标准目前还表示考虑了模板参数的命名空间,因此将其operator==放入namespace C将起作用(因为 std::pair 的第一个模板参数来自那里):https ://godbolt.org/z/eV8Joj

但是,1. 这与您的类型别名不太吻合,2. 有一些动作可以使 ADL 不那么狂野,我已经看到讨论摆脱“考虑模板参数的命名空间”。见http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0934r0.pdf

为什么我们要研究模板参数的命名空间?除非模板参数也是基类或其他东西,否则其中的任何内容都不可能是该类型接口的一部分。——赫伯萨特

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

于 2020-01-16T13:42:08.297 回答
5

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

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

REQUIRE((t1 == t2));

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

文档在更复杂的表达式的上下文中提到了这个问题。我不确定为什么会在您的情况下触发这种情况,但是请从堆栈跟踪中注意到您operator==实际上并未被调用,而是Catch::BinaryExpr::operator==andCatch::compareEqual似乎无法访问(或以其他方式选择不使用)您的实现. 无论哪种方式,解决方案都是禁用上述分解机制。

于 2020-01-16T13:26:03.800 回答