How to solve ambiguity in operator overloading emb

2019-09-10 08:54发布

问题:

In the following code, the g++ compiler surprisingly cannot decide which operator to use when they are embedded in a struct to serve as a comparator argument in a set:

#include <string>
#include <set>

struct KeyWord {
  std::string str;
  int qt;
  KeyWord(const std::string aKw = "", const int aQt = 0) : str(aKw), qt(aQt) {}
};

struct CompareKeywords {
  bool operator() (const std::string& left, const std::string& right) const {
    if (left.size() > right.size()) return true;
    else if (left.size() < right.size()) return false;
    else return (left < right);
  }
  bool operator() (const KeyWord& left, const KeyWord& right) {
    if (left.str.size() > right.str.size()) return true;
    else if (left.str.size() < right.str.size()) return false;
    else return (left.str < right.str);
  }
};

int main() {
  std::set<std::string, CompareKeywords> a;
  std::set<KeyWord, CompareKeywords> b;
  std::string s("_s_");
  KeyWord k("_k_", 1);
  a.insert(s);
  b.insert(k);
}

Here is the compiler output:

g++ oa.cpp
/usr/include/c++/4.9/bits/stl_tree.h: In instantiation of ‘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 _Key = std::basic_string<char>; _Val = std::basic_string<char>; _KeyOfValue = std::_Identity<std::basic_string<char> >; _Compare = CompareKeywords; _Alloc = std::allocator<std::basic_string<char> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::key_type = std::basic_string<char>]’:
/usr/include/c++/4.9/bits/stl_tree.h:1498:47:   required from ‘std::pair<std::_Rb_tree_iterator<_Val>, bool> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_unique(const _Val&) [with _Key = std::basic_string<char>; _Val = std::basic_string<char>; _KeyOfValue = std::_Identity<std::basic_string<char> >; _Compare = CompareKeywords; _Alloc = std::allocator<std::basic_string<char> >]’
/usr/include/c++/4.9/bits/stl_set.h:502:29:   required from ‘std::pair<typename std::_Rb_tree<_Key, _Key, std::_Identity<_Key>, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator, bool> std::set<_Key, _Compare, _Alloc>::insert(const value_type&) [with _Key = std::basic_string<char>; _Compare = CompareKeywords; _Alloc = std::allocator<std::basic_string<char> >; typename std::_Rb_tree<_Key, _Key, std::_Identity<_Key>, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator = std::_Rb_tree_const_iterator<std::basic_string<char> >; std::set<_Key, _Compare, _Alloc>::value_type = std::basic_string<char>]’
oa.cpp:28:13:   required from here
oa.cpp:11:8: note: candidate 1: bool CompareKeywords::operator()(const string&, const string&) const
   bool operator() (const std::string& left, const std::string& right) const {
        ^
oa.cpp:16:8: note: candidate 2: bool CompareKeywords::operator()(const KeyWord&, const KeyWord&)
   bool operator() (const KeyWord& left, const KeyWord& right) {
        ^
oa.cpp:11:8: note: candidate 1: bool CompareKeywords::operator()(const string&, const string&) const
   bool operator() (const std::string& left, const std::string& right) const {
        ^
oa.cpp:16:8: note: candidate 2: bool CompareKeywords::operator()(const KeyWord&, const KeyWord&)
   bool operator() (const KeyWord& left, const KeyWord& right) {
        ^
/usr/include/c++/4.9/bits/stl_tree.h: In instantiation of ‘std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_(std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr, std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr, const _Val&) [with _Key = std::basic_string<char>; _Val = std::basic_string<char>; _KeyOfValue = std::_Identity<std::basic_string<char> >; _Compare = CompareKeywords; _Alloc = std::allocator<std::basic_string<char> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator = std::_Rb_tree_iterator<std::basic_string<char> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr = std::_Rb_tree_node_base*]’:
/usr/include/c++/4.9/bits/stl_tree.h:1502:38:   required from ‘std::pair<std::_Rb_tree_iterator<_Val>, bool> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_unique(const _Val&) [with _Key = std::basic_string<char>; _Val = std::basic_string<char>; _KeyOfValue = std::_Identity<std::basic_string<char> >; _Compare = CompareKeywords; _Alloc = std::allocator<std::basic_string<char> >]’
/usr/include/c++/4.9/bits/stl_set.h:502:29:   required from ‘std::pair<typename std::_Rb_tree<_Key, _Key, std::_Identity<_Key>, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator, bool> std::set<_Key, _Compare, _Alloc>::insert(const value_type&) [with _Key = std::basic_string<char>; _Compare = CompareKeywords; _Alloc = std::allocator<std::basic_string<char> >; typename std::_Rb_tree<_Key, _Key, std::_Identity<_Key>, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator = std::_Rb_tree_const_iterator<std::basic_string<char> >; std::set<_Key, _Compare, _Alloc>::value_type = std::basic_string<char>]’
oa.cpp:28:13:   required from here
oa.cpp:11:8: note: candidate 1: bool CompareKeywords::operator()(const string&, const string&) const
   bool operator() (const std::string& left, const std::string& right) const {
        ^
oa.cpp:16:8: note: candidate 2: bool CompareKeywords::operator()(const KeyWord&, const KeyWord&)
   bool operator() (const KeyWord& left, const KeyWord& right) {
        ^

The last lines show the ambiguity where the compiler shows two candidates.

Why this ambiguity exist? How should I supress it?

回答1:

It looks like some builds of gcc have this peculiar feature of printing these messages out of the blue. For example all builds on coliru do this.

These messages are not errors because the object file is produced, and they are not warnings because -Werror doesn't turn them into errors. They look rather like a compiler bug. Obviously one cannot suppress these non-warnings with compiler flags.

Same exact versions of gcc on my machine don't print any messages with this code. They do print regular (tagged with the coloured "warning", non-suppressible, but turnable-to-error) warnings with similar code.

On coliru, making the second operator() const suppresses the messages.



回答2:

Two separate struct with only one operator in each of them dedicated to a type solves the problem:

#include <string>
#include <set>

struct KeyWord {
  std::string str;
  int qt;
  KeyWord(const std::string aKw = "", const int aQt = 0) : str(aKw), qt(aQt) {}
};

struct CompareStrings {
  bool operator() (const std::string& left, const std::string& right) const {
    if (left.size() > right.size()) return true;
    else if (left.size() < right.size()) return false;
    else return (left < right);
  }
};
struct CompareKeywords {
   bool operator() (const KeyWord& left, const KeyWord& right) {
    if (left.str.size() > right.str.size()) return true;
    else if (left.str.size() < right.str.size()) return false;
    else return (left.str < right.str);
  }
};

int main() {
  std::set<std::string, CompareStrings> a;
  std::set<KeyWord, CompareKeywords> b;
  std::string s("_s_");
  KeyWord k("_k_", 1);
  a.insert(s);
  b.insert(k);
}


回答3:

There was an error in the initial code:

  bool operator() (const std::string& left, const std::string& right) const {
  bool operator() (const KeyWord& left, const KeyWord& right) {

Suppressing the const at the end of the first declaration, or adding one to the second one solves the problem. But still, I don't understand why the compiler was confused.

So, either:

  bool operator() (const std::string& left, const std::string& right) {
  bool operator() (const KeyWord& left, const KeyWord& right) {

or:

  bool operator() (const std::string& left, const std::string& right) const {
  bool operator() (const KeyWord& left, const KeyWord& right) const {

works.

Note: Wheither a const function or not is discussed here.

Since I want overloading, both functions are expected to have the same behaviour, so const to both or none. If I would have liked different behaviours with one with const and the other without (in the case I would have had some struct members I would have wanted to modify), the second solution below with separate struct for each operator definition would be the solution.