Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to specialise operator<< for std::ostream and std::vector with generic template functions?

I am having trouble with the two-phase look-up as specified by the standard and (correctly) implemented by clang in connection with an overload of operator<< for std::ostream and std::vector.

Consider a very generic template function which shifts its argument into a stream (really useful only with recursion, but the simple example is enough to trigger the problem):

// generic.h
template<typename Stream, typename Arg>
void shift(Stream& s, Arg& arg) { s << arg; }

This generic.h may be used throughout a project. Then in some other file, we want to output a std::vector, so we define an overload

// vector.h
#include <iostream>
#include <vector>
std::ostream& operator<<(std::ostream& s, std::vector<int> const& v) {
  for(auto const& elem : v) { s << elem << ", "; }
  return s;
}

And the main file, we firstly (indirectly) use the generic.h and then, due to some other include, the vector overload:

// main.cpp
#include "generic.h"
#include "vector.h"

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

This code is accepted by GCC (5.4.0) and ICC (16.0), but clang complains call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup.

The annoying thing is that clang is right and I’d like to fix this in my code. There are as far as I can see three options:

  • Move the definition of operator<< before shift(). This has the disadvantage that when including some (possibly other) files which indirectly include generic.h and vector.h, one would also have to take care to order them correctly.

  • Use a custom namespace, import everything needed from std into that namespace and define the operator on the new-namespace classes inside that namespace, so that ADL can find it.

  • Define operator<< in the std namespace. I think this is undefined behaviour.

Did I miss any option? What would be the best way in general to define overloads for functions of std-only classes (the issue does not exist if I want to shift NS::MyClass, since then I can just define the operator in NS).

like image 456
Claudius Avatar asked Aug 01 '16 14:08

Claudius


2 Answers

Don't overload operators for types you don't control, such as:

std::ostream& operator<<(std::ostream& s, std::vector<int> const& v);

Instead create a tiny adaptor class and define the operator for that, for example:

template<typename T> struct PrintableVector {
  std::vector<T> const* vec;
}

template<typename T>
std::ostream& operator<<(std::ostream& s, PrintableVector<T> v) {
  for(auto const& elem : *v.vec) { s << elem << ", "; }
  return s;
}

That can be used like:

shift(std::cout, PrintableVector<int>{&v});

You can put the adaptor in whatever namespace you like, and put the overloaded operator in the same namespace so it can be found by ADL.

That avoids lookup problems, doesn't require adding anything to namespace std, and doesn't try to uniquely define what it means to print a vector<int> (which might cause problems in other parts of the program if some other code assumes vectors are not printable, or tries to define its own overloads for them).

like image 193
Jonathan Wakely Avatar answered Nov 15 '22 03:11

Jonathan Wakely


I followed Jonathan’s advice and used a wrapper Printable<> to define the operator<<. By making this wrapper also implicitly-convertible to the original type, I can handle both cases where only Printable<T> is printable as well as those were also T itself was printable. Code as follows:

template<typename T>
struct Printable {
  T const& ref;
  Printable(T const& ref) : ref(ref) { }
  operator T const& () { return ref; }
};

template<typename T>
Printable<T> printable(T const& in) { return Printable<T>(in); }

template<typename Stream, typename Arg>
void shift(Stream& s, Arg& arg) {
  s << printable(arg);
}

#include <iostream>
#include <vector>
std::ostream& operator<<(std::ostream& out, Printable<std::vector<int> > const& v) {
  for(auto const& elem : v.ref) { s << elem << ", "; }
  return s;
}

struct MyClass { };
std::ostream& operator<<(std::ostream& s, MyClass const& m) {
  return s << "MyClass\n";
}

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

  shift(std::cout, v);
  shift(std::cout, m);
}

This has the advantage that at the point of use in the call to shift(), I don’t have to care what sort of type my variable has. Only in the definition of operator<< for classes I have to be careful as well as when using such an operator.

like image 29
Claudius Avatar answered Nov 15 '22 04:11

Claudius