Home > Software engineering >  Error while trying to overload operator << for all std container printing, why?
Error while trying to overload operator << for all std container printing, why?

Time:08-10

I am trying to build an operator << overload to print all the standard library container types. So far, the overload works well, but when I use it in a generic program, it breaks when I try to print a simple std::string. In fact, this program:

#include <iostream>
#include <utility>
#include <vector>
#include <map>
#include <string>

// Helper Function to Print Test Containers (Vector and Map)
template <typename T, typename U>
inline std::ostream& operator<<(std::ostream& out, const std::pair<T, U>& p) {
    out << "[" << p.first << ", " << p.second << "]";
    return out;
} 

template <template <typename, typename...> class ContainerType, typename 
ValueType, typename... Args>
std::ostream& operator <<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
    for (const auto& v : c) {
        os << v << ' ';
    }
  return os;
}

int main()
 {
  std::vector <int> v = { 1,2,3 };
  std::cout << v;

  std::map <int,int> m = { { 1, 1} , { 2, 2 }, { 3, 3 } };
  std::cout << m;

  std::string s = "Test";
  std::cout << s;
 }

Gives me this error:

prove.cpp: In function ‘int main()’:
prove.cpp:32:13: error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’})
   32 |   std::cout << s;
      |   ~~~~~~~~~ ^~ ~
      |        |       |
      |        |       std::string {aka std::__cxx11::basic_string<char>}
      |        std::ostream {aka std::basic_ostream<char>}
prove.cpp:16:15: note: candidate: ‘std::ostream& operator<<(std::ostream&, const ContainerType<ValueType, Args ...>&) [with ContainerType = std::__cxx11::basic_string; ValueType = char; Args = {std::char_traits<char>, std::allocator<char>}; std::ostream = std::basic_ostream<char>]’
   16 | std::ostream& operator <<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
      |               ^~~~~~~~
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 prove.cpp:1:
/usr/include/c  /9/bits/basic_string.h:6419:5: note: candidate: ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::__cxx11::basic_string<_CharT, _Traits, _Allocator>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’
 6419 |     operator<<(basic_ostream<_CharT, _Traits>& __os,
      |     ^~~~~~~~

I think that the problem is due to the fact that this overload is used to print also simple std::string objects, but I didn't find any suitable way to solve this so far. Any help? thanks.

CodePudding user response:

Your operator<< overload may match types for which an operator<< overload is already defined. I suggest that you disable it for such types:

#include <type_traits>

template <template <typename, typename...> class ContainerType,
          typename ValueType, typename... Args>
std::enable_if_t<!is_streamable_v<ContainerType<ValueType, Args...>>,
                 std::ostream&>
operator<<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
    for (const auto& v : c) {
        os << v << ' ';
    }
    return os;
}

The enable_if_t line uses SFINAE to disable the function for types that are already streamable.

The type trait is_streamable_v that is used above could look like this:

template<class T>
struct is_streamable {
    static std::false_type test(...);
    
    template<class U>
    static auto test(const U& u) -> decltype(std::declval<std::ostream&>() << u,
                                             std::true_type{});

    static constexpr bool value = decltype(test(std::declval<T>()))::value;
};

// helper variable template:
template<class T>
inline constexpr bool is_streamable_v = is_streamable<T>::value;

Your original program should now work as expected:

int main() {
  std::vector <int> v = { 1,2,3 };
  std::cout << v;

  std::map <int,int> m = { { 1, 1} , { 2, 2 }, { 3, 3 } };
  std::cout << m;

  std::string s = "Test";
  std::cout << s;
} 

Demo

CodePudding user response:

The issue is that your << is too generic. It matches std::cout << some_string; and potentially other types for which there is already a << overload. The solution is to not provide overloads for types you do not own (unless it is explicitly permitted).

With minimal changes you can refactor your << to be a named function, then write a wrapper:

template <typename T>
struct my_print {
     T& t;
};
tempalte <typename T>
std::ostream& operator<<(std::ostream& out,const my_print<T>& mp) {
    // call your named funtion template
    do_the_printing(out,mp);
    return out;
}

The caveat is that you need to explicitly create the instance:

std::cout << my_print(some_stl_container);

Of course you can also let your function template print directly and call it like this

do_the_print(std::cout,some_stl_container);
  • Related