C++踩坑--set与重载<

发布时间 2023-09-25 18:11:55作者: ydqun

set与重载<

set是有序容器,在定义容器的时候必须要指定 key 的比较函数。只不过这个函数通常是默认的 less,表示小于关系,不用特意写出来:

template<
    class Key,                      // 模板参数是key类型,即元素类型
    class Compare = std::less<Key>  // 比较函数
> class set;                        // 集合

C++ 里的 int、string 等基本类型都支持比较排序,放进有序容器里毫无问题。但很多自定义类型没有默认的比较函数,要作为容器的 key 就有点麻烦。虽然这种情况不多见,但有的时候还真是个“刚性需求”。解决这个问题一般是重载“<”,比如说我们有一个 Point 类,它是没有大小概念的,但只要给它重载“<”操作符,就可以放进有序容器里了:

/*************************************************************************
        > File Name: 03-set.cpp
        > Author:
        > Mail:
        > Created Time: Mon 25 Sep 2023 05:04:02 PM CST
 ************************************************************************/
#include <set>
#include <iostream>
using namespace std;

class Point
{
public:
    Point(double x, double y) : m_x(x), m_y(y) {}
    ~Point() {}
    bool operator < (const Point& rhs)
    {
        return m_x > rhs.m_x;
    }
private:
    double m_x, m_y;
};


int main()
{
    set<Point> s;
    s.emplace(Point(1.1, 2.2));
    s.emplace(Point(2.2, 3.3));
    return 0;
}

由于我是一个新手C++使用者,上述代码尽管重载了小于(<)运算符,但是编译报错:

ydqun@ydqhost chapter12 % g++ 03-set.cpp                                                                                                                                                                                            [0]
In file included from /usr/include/c++/9/bits/stl_tree.h:65,
                 from /usr/include/c++/9/set:60,
                 from 03-set.cpp:7:
/usr/include/c++/9/bits/stl_function.h: In instantiation of ‘constexpr bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = Point]’:
/usr/include/c++/9/bits/stl_tree.h:2095:11:   required from ‘std::pair<std::_Rb_tree_node_base*, std::_Rb_tree_node_base*> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_get_insert_unique_pos(const key_type&) [with _Ke
y = Point; _Val = Point; _KeyOfValue = std::_Identity<Point>; _Compare = std::less<Point>; _Alloc = std::allocator<Point>; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::key_type = Point]’
/usr/include/c++/9/bits/stl_tree.h:2413:19:   required from ‘std::pair<std::_Rb_tree_iterator<_Val>, bool> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_emplace_unique(_Args&& ...) [with _Args = {Point}; _Key = Point;
 _Val = Point; _KeyOfValue = std::_Identity<Point>; _Compare = std::less<Point>; _Alloc = std::allocator<Point>]’
/usr/include/c++/9/bits/stl_set.h:463:64:   required from ‘std::pair<typename std::_Rb_tree<_Key, _Key, std::_Identity<_Tp>, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator, bool> std::set<
_Key, _Compare, _Alloc>::emplace(_Args&& ...) [with _Args = {Point}; _Key = Point; _Compare = std::less<Point>; _Alloc = std::allocator<Point>; typename std::_Rb_tree<_Key, _Key, std::_Identity<_Tp>, _Compare, typename __gnu_cxx::__
alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator = std::_Rb_tree_const_iterator<Point>]’
03-set.cpp:28:30:   required from here
/usr/include/c++/9/bits/stl_function.h:386:20: error: no match for ‘operator<’ (operand types are ‘const Point’ and ‘const Point’)
  386 |       { return __x < __y; }
      |                ~~~~^~~~~
03-set.cpp:16:10: note: candidate: ‘bool Point::operator<(const Point&)’ <near match>
   16 |     bool operator < (const Point& rhs)
      |          ^~~~~~~~
03-set.cpp:16:10: note:   passing ‘const Point*’ as ‘this’ argument discards qualifiers
In file included from /usr/include/c++/9/bits/stl_algobase.h:64,
                 from /usr/include/c++/9/bits/stl_tree.h:63,
                 from /usr/include/c++/9/set:60,
                 from 03-set.cpp:7:
/usr/include/c++/9/bits/stl_pair.h:454:5: note: candidate: ‘template<class _T1, class _T2> constexpr bool std::operator<(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&)’
  454 |     operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
      |     ^~~~~~~~
/usr/include/c++/9/bits/stl_pair.h:454:5: note:   template argument deduction/substitution failed:
In file included from /usr/include/c++/9/bits/stl_tree.h:65,
                 from /usr/include/c++/9/set:60,
                 from 03-set.cpp:7:
/usr/include/c++/9/bits/stl_function.h:386:20: note:   ‘const Point’ is not derived from ‘const std::pair<_T1, _T2>’
  386 |       { return __x < __y; }
      |                ~~~~^~~~~
In file included from /usr/include/c++/9/bits/stl_algobase.h:67,
                 from /usr/include/c++/9/bits/stl_tree.h:63,
                 from /usr/include/c++/9/set:60,
                 from 03-set.cpp:7:
/usr/include/c++/9/bits/stl_iterator.h:331:5: note: candidate: ‘template<class _Iterator> bool std::operator<(const std::reverse_iterator<_Iterator>&, const std::reverse_iterator<_Iterator>&)’
...... 中间省略若几十行(无力吐槽C++编译出错提示)
/usr/include/c++/9/bits/stl_function.h:386:20: note:   ‘const Point’ is not derived from ‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>’
  386 |       { return __x < __y; }
      |                ~~~~^~~~~
In file included from /usr/include/c++/9/string:55,
                 from /usr/include/c++/9/bits/locale_classes.h:40,
                 from /usr/include/c++/9/bits/ios_base.h:41,
                 from /usr/include/c++/9/ios:42,
                 from /usr/include/c++/9/ostream:38,
                 from /usr/include/c++/9/iostream:39,
                 from 03-set.cpp:8:
/usr/include/c++/9/bits/basic_string.h:6239:5: note: candidate: ‘template<class _CharT, class _Traits, class _Alloc> bool std::operator<(const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&, const _CharT*)’
 6239 |     operator<(const basic_string<_CharT, _Traits, _Alloc>& __lhs,
      |     ^~~~~~~~
/usr/include/c++/9/bits/basic_string.h:6239:5: note:   template argument deduction/substitution failed:
In file included from /usr/include/c++/9/bits/stl_tree.h:65,
                 from /usr/include/c++/9/set:60,
                 from 03-set.cpp:7:
/usr/include/c++/9/bits/stl_function.h:386:20: note:   ‘const Point’ is not derived from ‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>’
  386 |       { return __x < __y; }
      |                ~~~~^~~~~
In file included from /usr/include/c++/9/string:55,
                 from /usr/include/c++/9/bits/locale_classes.h:40,
                 from /usr/include/c++/9/bits/ios_base.h:41,
                 from /usr/include/c++/9/ios:42,
                 from /usr/include/c++/9/ostream:38,
                 from /usr/include/c++/9/iostream:39,
                 from 03-set.cpp:8:
/usr/include/c++/9/bits/basic_string.h:6251:5: note: candidate: ‘template<class _CharT, class _Traits, class _Alloc> bool std::operator<(const _CharT*, const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&)’
 6251 |     operator<(const _CharT* __lhs,
      |     ^~~~~~~~
/usr/include/c++/9/bits/basic_string.h:6251:5: note:   template argument deduction/substitution failed:
In file included from /usr/include/c++/9/bits/stl_tree.h:65,
                 from /usr/include/c++/9/set:60,
                 from 03-set.cpp:7:
/usr/include/c++/9/bits/stl_function.h:386:20: note:   mismatched types ‘const _CharT*’ and ‘Point’
  386 |       { return __x < __y; }
      |                ~~~~^~~~~
In file included from /usr/include/c++/9/bits/ios_base.h:46,
                 from /usr/include/c++/9/ios:42,
                 from /usr/include/c++/9/ostream:38,
                 from /usr/include/c++/9/iostream:39,
                 from 03-set.cpp:8:
/usr/include/c++/9/system_error:208:3: note: candidate: ‘bool std::operator<(const std::error_code&, const std::error_code&)’
  208 |   operator<(const error_code& __lhs, const error_code& __rhs) noexcept
      |   ^~~~~~~~
/usr/include/c++/9/system_error:208:31: note:   no known conversion for argument 1 from ‘const Point’ to ‘const std::error_code&’
  208 |   operator<(const error_code& __lhs, const error_code& __rhs) noexcept
      |             ~~~~~~~~~~~~~~~~~~^~~~~
/usr/include/c++/9/system_error:282:3: note: candidate: ‘bool std::operator<(const std::error_condition&, const std::error_condition&)’
  282 |   operator<(const error_condition& __lhs,
      |   ^~~~~~~~
/usr/include/c++/9/system_error:282:36: note:   no known conversion for argument 1 from ‘const Point’ to ‘const std::error_condition&’
  282 |   operator<(const error_condition& __lhs,
      |             ~~~~~~~~~~~~~~~~~~~~~~~^~~~~

相信不少新入坑C++的同学看到这类报错后头都大了(我也是)。
经过我长达半个小时的排查,终于从出错提示中找到答案,在报错信息中的第四行,如下:

/usr/include/c++/9/bits/stl_function.h: In instantiation of ‘constexpr bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = Point]’:

这时候,我们可以知道是因为在类内重载<运算符时,少了const修饰,导致函数签名不匹配,我们只需要对重载的operator<()函数后面加上const修饰,即可解决,完整代码如下:

/*************************************************************************
        > File Name: 03-set.cpp
        > Author:
        > Mail:
        > Created Time: Mon 25 Sep 2023 05:04:02 PM CST
 ************************************************************************/
#include <set>
#include <iostream>
using namespace std;

class Point
{
public:
    Point(double x, double y) : m_x(x), m_y(y) {}
    ~Point() {}
    bool operator < (const Point& rhs) const
    {
        return m_x > rhs.m_x;
    }
private:
    double m_x, m_y;
};


int main()
{
    set<Point> s;
    s.emplace(Point(1.1, 2.2));
    s.emplace(Point(2.2, 3.3));
    return 0;
}