我偶然发现在std::set
的帮助下使用透明比较器对std::less
进行了非常奇怪的编译错误。考虑这个简单的程序:
using Key = std::string;
bool operator<(const Key&, int) { return true; }
bool operator<(int, const Key&) { return true; }
int main()
{
std::set<Key, std::less<>> s;
int x;
auto it = s.find(x);
}
它给了我编译错误:
error: no matching function for call to object of type 'const std::less<void>'
if (__j != end() && _M_impl._M_key_compare(__k, _S_key(__j._M_node)))
^~~~~~~~~~~~~~~~~~~~~~
如果我使用自己的类而不是std::string
作为键,则可以正常工作:
struct My {};
bool operator<(const My&, const My&) { return true; }
using Key = My;
为什么它对std::string
不起作用?
在此处查看演示:https://gcc.godbolt.org/z/MY-Y2s
UPD
我真正想做的是在std::unique_ptr<T>
和T*
之间声明比较运算符。但是我认为使用std::string
会更清楚。
答案 0 :(得分:2)
Argument-dependent lookup是一个有趣的老东西,不是吗?
在命名空间operator<
中已经存在与std::string
相关的std
,在寻找适合您参数的<
时可以找到。也许违反直觉(但并非没有充分的理由),之后不再尝试进一步查找!不搜索其他名称空间。即使只有重载实际上匹配了两个参数。您的全局operator<
在这种情况下实际上是隐藏的,如以下可怕的示例所示:
namespace N
{
struct Foo {};
bool operator<(Foo, Foo) { return false; }
}
bool operator<(N::Foo, int) { return false; }
namespace N
{
template <typename T1, typename T2>
bool less(T1 lhs, T2 rhs)
{
return lhs < rhs;
}
}
int main()
{
N::Foo f;
N::less(f, 3);
}
/*
main.cpp: In instantiation of 'bool N::less(T1, T2) [with T1 = N::Foo; T2 = int]':
main.cpp:22:17: required from here
main.cpp:15:20: error: no match for 'operator<' (operand types are 'N::Foo' and 'int')
return lhs < rhs;
~~~~^~~~~
*/
现在,您不能将任何东西添加到命名空间std
中,但这很好,因为如果您仍然不重载与其他人的类型相关的运算符,将会好得多 。当某些其他库执行相同的操作时,这是隐藏ODR错误的最快方法。
类似地,伪装地创建{em}实际上只是Key
的{{1}}是导致意外冲突和意外行为的秘诀。
总体而言,我强烈建议将您的std::string
至少设为Key
的“强别名”;也就是说,它是自己的类型,而不是简单的别名。正如您所发现的,这也解决了您的问题,因为现在std::string
和操作数类型位于同一名称空间中。
更一般而言,如果您不是真正在这里使用别名,但确实希望对标准类型进行操作,那么您将返回编写命名的自定义比较器,该自定义比较器很好地隔离了新逻辑,并且使用起来也很简单。缺点当然是您每次都必须“选择加入”,但我认为总体而言值得这样做。