无法将用户提供的比较功能用于std :: set <std :: string,std :: less <=“” >>

时间:2019-01-25 17:26:36

标签: c++ compiler-errors stl

我偶然发现在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会更清楚。

1 个答案:

答案 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;
                ~~~~^~~~~
*/

live demo

现在,您不能将任何东西添加到命名空间std中,但这很好,因为如果您仍然不重载与其他人的类型相关的运算符,将会好得多 。当某些其他库执行相同的操作时,这是隐藏ODR错误的最快方法。

类似地,伪装地创建{em}实际上只是Key的{​​{1}}是导致意外冲突和意外行为的秘诀。

总体而言,我强烈建议将您的std::string至少设为Key的“强别名”;也就是说,它是自己的类型,而不是简单的别名。正如您所发现的,这也解决了您的问题,因为现在std::string和操作数类型位于同一名称空间中。

更一般而言,如果您不是真正在这里使用别名,但确实希望对标准类型进行操作,那么您将返回编写命名的自定义比较器,该自定义比较器很好地隔离了新逻辑,并且使用起来也很简单。缺点当然是您每次都必须“选择加入”,但我认为总体而言值得这样做。