2

The code below fails to compile with gcc (9.2) and c++17. It does work with clang and MSVC, it also worked up to c++14. What's going on, who is right and is there an easy workaround? Resorted to #defineing the unordered_set overload away for gcc but I'd prefer a 'clean' solution.

#include <unordered_set>
#include <iostream>

struct Stream
{};

template <typename T, typename Alloc, template<typename, typename> class Container>
Stream & operator<< (Stream &stream, const Container<T, Alloc>& container)
{
  std::cout << "container" << std::endl;
  return stream;
}

template <class K, class Hasher, class Keyeq, class Alloc>
Stream & operator<< (Stream &stream, const std::unordered_set<K, Hasher, Keyeq, Alloc> & container)
{
  std::cout << "unordered_set" << std::endl;
  return stream;
}

int main()
{
    Stream t;
    std::unordered_set<int> set;
    t << set;

    return 0;
}

Result:

<source>: In function 'int main()':    
<source>:25:7: error: ambiguous overload for 'operator<<' (operand types are 'Stream' and 'std::unordered_set<int>')    
   25 |     t << set;    
      |     ~ ^~ ~~~    
      |     |    |    
      |     |    std::unordered_set<int>    
      |     Stream    
<source>:8:10: note: candidate: 'Stream& operator<<(Stream&, const Container<T, Alloc>&) [with T = int; Alloc = std::hash<int>; Container = std::unordered_set]'    
    8 | Stream & operator<< (Stream &stream, const Container<T, Alloc>& container)    
      |          ^~~~~~~~    
<source>:15:10: note: candidate: 'Stream& operator<<(Stream&, const std::unordered_set<_Value1, _Hash1, _Pred1, _Alloc1>&) [with K = int; Hasher = std::hash<int>; Keyeq = std::equal_to<int>; Alloc = std::allocator<int>]'    
   15 | Stream & operator<< (Stream &stream, const std::unordered_set<K, Hasher, Keyeq, Alloc> & container)    
      |          ^~~~~~~~    
ASM generation compiler returned: 1    
<source>: In function 'int main()':    
<source>:25:7: error: ambiguous overload for 'operator<<' (operand types are 'Stream' and 'std::unordered_set<int>')    
   25 |     t << set;    
      |     ~ ^~ ~~~    
      |     |    |    
      |     |    std::unordered_set<int>    
      |     Stream    
<source>:8:10: note: candidate: 'Stream& operator<<(Stream&, const Container<T, Alloc>&) [with T = int; Alloc = std::hash<int>; Container = std::unordered_set]'    
    8 | Stream & operator<< (Stream &stream, const Container<T, Alloc>& container)    
      |          ^~~~~~~~    
<source>:15:10: note: candidate: 'Stream& operator<<(Stream&, const std::unordered_set<_Value1, _Hash1, _Pred1, _Alloc1>&) [with K = int; Hasher = std::hash<int>; Keyeq = std::equal_to<int>; Alloc = std::allocator<int>]'    
   15 | Stream & operator<< (Stream &stream, const std::unordered_set<K, Hasher, Keyeq, Alloc> & container)    
      |          ^~~~~~~~    
Execution build compiler returned: 1

https://godbolt.org/z/4dGu6L

Barry
  • 286,269
  • 29
  • 621
  • 977
Adversus
  • 2,166
  • 20
  • 23

1 Answers1

4

gcc is correct; this is because in C++17 mode it implements DR P0522's resolution to CWG 150, allowing default template parameters to be ignored when matching a template template parameter:

template <template <typename> class> void FD();
template <typename, typename = int> struct SD { /* ... */ };
FD<SD>();  // OK; error before this paper (CWG 150)

The other compilers do not (yet) implement P0522 and therefore do not see the generic overload as viable.

Given that it is viable, and under the rules for partial ordering of function templates in C++17, neither respective second argument can be deduced to the other (Container<T, Alloc> vs. std::unordered_set<K, Hasher, Keyeq, Alloc>) and so the overloads are ambiguous. The solution as mentioned in comments is to make the generic overload more generic, such that std::unordered_set<K, Hasher, Keyeq, Alloc> can be deduced to it; for example:

template <typename T, class... Ts, template<typename, class...> class Container>
Stream & operator<< (Stream &stream, const Container<T, Ts...>& container)

Here std::unordered_set<K, Hasher, Keyeq, Alloc> deduces to Container<T, Ts...> (with Container = std::unordered_set, T = K, Ts = {Hasher, Keyeq, Alloc}) and so the overloads can be partially ordered.

This should work in both C++14 and C++17, and later.

ecatmur
  • 152,476
  • 27
  • 293
  • 366