【问题标题】:Cannot use user-provided comparison function for std::set<std::string, std::less<>>不能对 std::set<std::string, std::less<>> 使用用户提供的比较函数
【发布时间】:2019-01-25 17:26:36
【问题描述】:

我在std::less 的帮助下使用透明比较器偶然发现了std::set 的非常奇怪的编译错误。考虑这个简单的程序:

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&lt;T&gt;T* 之间声明比较运算符。但我认为std::string 会更清楚。

【问题讨论】:

  • std::less&lt;Key&gt;,不是吗?
  • 您将苹果与橙子进行比较。您的 My 实现需要 2 个 My。您的string 实现采用intstring。这要求比较器是透明的。
  • 标准是否允许定义不涉及任何用户提供类型的运算符重载?
  • @eerorika std::string 被认为是用户提供的类型。虽然你不应该为你不拥有的类型重载运算符。

标签: c++ compiler-errors stl


【解决方案1】:

Argument-dependent lookup 是个有趣的老东西,不是吗?

在命名空间std 中已经存在与std::string 相关的operator&lt;,并且在寻找适合您的论点的&lt; 时会找到该std::string。也许违反直觉(但并非没有充分的理由),此后不再尝试进一步查找!不搜索其他名称空间。即使只有您的重载实际上匹配两个参数。您的全局 operator&lt; 在此上下文中有效隐藏,如下面的可怕示例所示:

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 错误的最快方法。

类似地,创建一个名为Key 的东西实际上只是伪装成std::string,这会导致意外冲突和意外行为。

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

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

【讨论】:

  • 我敢肯定我有一点错误,因为 ADL 让我大吃一惊 - 如果需要,请随时进行编辑。但我认为答案的精神是正确的
  • 非常感谢您的详细解释。我完全同意像我那样声明operator&lt; 没有多大意义。但是,您可以猜到,这是一个简化的示例。我真正想做的是介绍std::unique_ptr&lt;T&gt;T*之间的比较。此设置产生完全相同的错误。当然,我可以隐藏自定义透明比较器中的比较运算符,但我只是感兴趣如何在没有自定义比较器的情况下做到这一点。说,我不想在我使用的每个关联容器中提供它们。有什么想法吗?..
  • @Mikhail 不是随手;自定义比较器似乎是显而易见的最佳解决方案。我确实理解“透明地”获得这种行为的诱惑,但我认为这可能是一个黑暗的诱惑,因为这种透明度很可能会导致你走上邪恶的道路。如果你甚至可以让它工作,那就是。
  • 好吧,我不清楚为什么我们一开始就没有开箱即用的智能指针和原始指针之间的比较。顺便说一句,如果我将比较运算符放入命名空间 std :3,我可以让它工作
  • @Mikhail 好吧,您可能会惊讶地发现the result of p1 &lt; p2, if they don't point to the same object or array, is unspecified。您不应该将指针视为数字,仅仅因为它们的实现通常(好吧,很好,总是)类似数字地址的东西。因此,如果我们不能比较任意指针,那么标准没有为我们提供一种将任意原始指针与任意智能指针进行比较的方法也就不足为奇了。但是,如果您从 .get() 获得原始指针,它可能是有效的。
猜你喜欢
  • 2014-04-14
  • 1970-01-01
  • 1970-01-01
  • 2012-02-06
  • 2017-07-28
  • 2013-01-31
  • 1970-01-01
  • 2010-11-06
  • 2016-07-28
相关资源
最近更新 更多