Is it possible to define operator<< for templated types?

Viewed 101

I want to print values of STL types like std::maps, for example:

int main()
{
    std::map<int, std::vector<int>> t = {{1, {2, 3}}, {4, {5}}};
    std::cout << t << std::endl;
    return 0;
}

This fails because operator<< isn't defined for these types.

If I try to define it myself, I have to also forward declare my definitions, otherwise the compilation fails with:

error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
        o << sep << "{" << x.first << ", " << x.second << "}";
...
note: 'operator<<' should be declared prior to the call site std::ostream& operator<< (std::ostream& o, const std::vector<T>& vec)

That is unfortunate because having to forward declare the methods would require using two includes for each type (forward declarations first and then implementations).

  • Is there a way to define operator<< for templated types in a modular way?
  • Is this difficulty the reasoning behind why operator<< isn't available for STL types out of the box?

Reference: A working implementation with forward declarations

1 Answers

In your gist, the operator<< overload for std::map<K, V> is defined before the overload for std::vector<T>.

GCC gladly accepts your code it without raising any errors, while Clang (the one you are using I guess) outright refuses to compile it with the message you have posted, which basically says that that there's no good operator<< for std::vector<T> has been defined yet.

I should double check what the standard specifies in this situations, but my first impression is that GCC is accepting your code because operator<<(ostream&,const std::vector<T>&) is defined at instantiation time (which happens in main(), when you call std::cout << t), while Clang strictly considers as visible only those functions that are known at the time the template has been defined.

If you add a forward declaration for operator<<(ostream&,const std::vector<T>&), it becomes unambiguously visible in operator<<(ostream&, const std::map<K,V>) and Clang compiles your code.

Just swapping the functions is also enough to get your specific code to build under Clang, but it will break again if you attempt to do

int main()
{
    std::vector<std::map<int, int>> u = {{{1, 2},{3, 4}}, {{4, 5}}};

    std::cout << u << std::endl;

    return 0;
}

because now Clang will not see the correct overload for std::map<K, V> inside the implementation for std::vector<T> (with GCC it will still build fine though)

Related