我有一个使用 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==
函数。
如果我不在CatchREQUIRE
宏中进行比较,那么它可以工作:
auto result = t1 == t2; // Invokes my overloaded comparison operator
REQUIRE(result);
现在这是 Catch 的问题,还是我的运算符函数的问题?
注意:我正在 Debian SID 上构建 GCC 9.2 的最新版本
$ 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.
请注意,即使使用 Lightness 建议的括号,您显示的代码也非常脆弱。
我猜您最初由于宏内的依赖名称查找而处于仅 ADL 的领域(请参阅 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==
,因为非限定名称查找在具有任何operator==
的第一个封闭作用域中停止。由于这找不到任何有用的东西,因此它回退到使用内置的std::pair
比较运算符(通过 ADL 找到(,这将不起作用。
只需将运算符重载放在它们所操作的对象的命名空间中即可。因此,不要为来自std
(或不允许触摸的其他命名空间(的设施重载运算符。
从评论添加:
该标准目前还说考虑了模板参数的命名空间,因此将operator==
放在namespace C
中是可行的(因为std::p air的第一个模板参数来自那里(:https://godbolt.org/z/eV8Joj
但是,1. 这与您的类型别名不太吻合,2. 有一些运动可以使 ADL 不那么狂野,我已经看到讨论摆脱"考虑模板参数的命名空间"。请参阅 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0934r0.pdf:
我们到底为什么要研究模板参数的命名空间?其中的任何内容都不可能是类型接口的一部分,除非模板参数也是基类或其他东西。- 赫伯·萨特
我不知道这篇论文今天的情况,但我会避免在新代码中依赖这种 ADL。
为了提供良好的诊断输出而扩展操作数的魔力有时会失败。
解决方法是用一些括号禁用它:
REQUIRE((t1 == t2));
这实际上与变量的解决方法相同。
文档在更复杂的表达式上下文中提到了此问题。我不确定为什么在您的情况下触发这种情况,但从堆栈跟踪中注意到您的operator==
实际上并没有被调用,而是Catch::BinaryExpr::operator==
和Catch::compareEqual
,这似乎无法访问(或以其他方式选择不使用(您的实现。无论哪种方式,解决方案都是如上所述禁用分解机制。