Why can't the compiler find this operator<< overload?
Asked Answered
L

1

5

I'm trying to write overloads of operator<< for specific instantiations of standard library containers that will be stored in a boost::variant. Here's a small example that illustrates the problem:

#include <iostream>
#include <vector>

std::ostream & operator<<( std::ostream & os, const std::vector< int > & ) {
  os << "Streaming out std::vector< int >";
  return os;
}

std::ostream & operator<<( std::ostream & os, const std::vector< double > & ) {
  os << "Streaming out std::vector< double >";
  return os;
}

#include <boost/variant.hpp>

typedef boost::variant< std::vector< int >, std::vector< double > > MyVariant;

int main( int argc, char * argv[] ) {
  std::cout << MyVariant();
  return 0;
}

Clang's first error is

boost/variant/detail/variant_io.hpp:64:14: error: invalid operands to binary expression ('std::basic_ostream<char>' and 'const std::vector<int, std::allocator<int>>')
        out_ << operand;
        ~~~~ ^  ~~~~~~~

I realize that the #include <boost/variant.hpp> is in an odd place. I'm pretty sure the problem had to do with two-phase name lookup in templates, so I moved the #include in an attempt to implement fix #1 from the clang documentation on lookup. Fix #2 from that documentation isn't a good option because I believe adding my overloaded operator<< to the std namespace would lead to undefined behavior.

Shouldn't defining my operator<<s before the #include allow the compiler to find the definitions? That technique seems to work in the following example, adapted from the same clang page.

#include <iostream>

namespace ns {
  struct Data {};
}

std::ostream& operator<<(std::ostream& out, const ns::Data & data) {
  return out << "Some data";
}

namespace ns2 {
  template<typename T>
  void Dump( std::ostream & out, const T & value) {
    out << value;
  }
}

int main( int argc, char * argv[] ) {
  ns2::Dump( std::cout, ns::Data() );
}
Lajoie answered 15/9, 2013 at 20:34 Comment(0)
S
9

During template instantiation function template depending on a template type are only found during phase II look-up. Phase II look-up doesn't consider names visible at the point of use but only considers names found based on argument dependent look-up. Since the only associated namespace for std::ostream and std::vector<int> is namespace std it doesn't look for your output operators defined in the global namespace. Of course, you are not allowed to add these operators to namespace std which is a real catch: you can only define these operators for containers involving, at least, one user define type! On possibly way around this restriction is to add a custom allocator which is simply derived from std::allocator<T> but lives in a suitable user-define namespace: you can then define the output operators in this namespace. The drawback of this approach is that std::vector<T> (i.e., without an allocator parameter) is pretty much a vocabulary type.

Moving the declarations around doesn't help: phase II name look-up doesn't really depend on the order of the declaration except that the declarations have to precede the point of instantiation. The only proper fix is to define the operators in a namespace being sought by phase II look-up which pretty much means that the types being printed have to involve a user-defined type.

Sturtevant answered 15/9, 2013 at 20:55 Comment(7)
a quick and rather dirty fix is to do struct vI: std::vector<int> {}; and similary for the other vector, and then the code does what the OP wants because these types live in the global namespace.Perfervid
@TemplateRex: Agreed. ... and with C++11 the type can be made reasonably complete by using template <typename T> struct myvector: std::vector<int> { using std::vector<T>::vector; }; as the using declaration causes std::vector<T>'s constructors to be inherited.Jedidiah
or the variadic template alias to also take into account allocators. Ah my kingdom for opaque typedefs :-)Perfervid
If that's the case, then why does this example compile without errors? #include <iostream> namespace ns { struct Data {}; } std::ostream& operator<<(std::ostream& out, const ns::Data & data) { return out << "Some data"; } namespace ns2 { template<typename T> void Dump( std::ostream & out, const T & value) { out << value; } } int main( int argc, char * argv[] ) { ns2::Dump( std::cout, ns::Data() ); } edit: not sure how to format code in comments better, sorry. I'll edit the original question with this example.Lajoie
@user2781966: It seems, I got a bit carried away: 14.6.4 [temp.dep.res] indeed states names visible at the point of definition of the template are considered to resolve dependent names. However, it is problematic to rely on the order of declarations as doing so can easily result in different functions being looked up in different translation units which would result in a violation of the one definition rule (3.4 [basic.def.odr]): it seems that I mixed up what I concluded is a reasonable approach to overloading operators used by templates with what the standard actually mandates.Jedidiah
Then, based on 14.6.4, my first example should compile without errors (even though I agree relying on the order of declarations is bad). Maybe it's a bug in clang and gcc, as they both reject my first example?Lajoie
@user2781966: It seems it should compile but maybe my original statement wasn't that much off. It certainly helps to use myvector<int> as in the comment above.Jedidiah

© 2022 - 2024 — McMap. All rights reserved.