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() );
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With