代码之家  ›  专栏  ›  技术社区  ›  Mikhail

无法将用户提供的比较函数用于std::set<std::string,std::less<>>

  •  0
  • Mikhail  · 技术社区  · 7 年前

    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;
    

    为什么它不适用于 字符串 ?

    https://gcc.godbolt.org/z/MY-Y2s

    UPD

    我真正想做的是声明 std::unique_ptr<T> T* . 但我想这会更清楚 字符串 .

    1 回复  |  直到 7 年前
        1
  •  2
  •   Lightness Races in Orbit    7 年前

    Argument-dependent lookup 这是一件有趣的老事情,不是吗?

    operator< 关于 std::string 在命名空间中 std ,这是在查找 < 这符合你的论点。也许与直觉相反(但并非没有充分的理由), 之后!不搜索其他名称空间。即使只有重载实际上与这两个参数匹配。你的全球 操作员< 在这种情况下实际上是隐藏的,如以下可怕的示例所示:

    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

    现在,您不能向名称空间添加内容 ,但那很好,因为它会 好多了

    类似地,创建一个名为 Key 事实上 只是 字符串 伪装会导致意外冲突和意外行为。

    钥匙 至少有一个“强别名” 字符串 操作员<

    更一般地说,如果你不是 真正地 在这里使用别名,但确实希望对标准类型进行操作,现在又回到编写命名的自定义比较器上来了,它可以很好地隔离新逻辑,而且使用起来也非常简单。当然,缺点是你每次都必须“选择”它,但我认为总的来说这是值得的。