Why can't the compiler find this operator<< overload? - c++

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.

Related

Why is operator<< overload not found?

I've declared an operator<< overload for time_point in the global namespace and also one for a::Foo in the a namespace. When I try to print a time_point in a function also defined in namespace a, I get the error:
Invalid operands to binary expression.
Why is this, and what is the best solution?
#include <iostream>
#include <chrono>
std::ostream &operator<<(std::ostream &os,
const std::chrono::system_clock::time_point &time_point);
namespace a {
class Foo;
std::ostream &operator<<(std::ostream &os, const a::Foo &foo);
void print_time() {
std::cout << std::chrono::system_clock::now(); // error here!
}
}
int main() {
a::print_time();
}
The reason is simple: a::operator<< shadows your ::operator<< since they have the same name. This is why operators basically have to share a namespace with their classes: such shadowing is to be expected, and so ADL (as discussed in the comments) is the only reliable means of finding them.

Why can't std::variant find operator<() when not in same namespace as compared classes

I was trying to provide a custom operator< for a class from an external library. This class is within that library's namespace, however, the operator I wanted to define is not. Now if I define a std::variant and want to use it in a std::set, the compilation fails as it can't detect the operator<. Here's an example (godbolt):
#include <variant>
#include <set>
#include <string>
namespace myClasses
{
struct classA
{
classA(const unsigned int i) :i(i) {};
int i;
};
struct classB
{
classB(const unsigned int u) :u(u) {};
unsigned int u;
};
}// namespace myClasses
//namespace myClasses { //<- uncomment this
bool operator<(const myClasses::classA &v, const myClasses::classA &w)
{
return v.i < w.i;
}
bool operator<(const myClasses::classB &v, const myClasses::classB &w)
{
return v.u < w.u;
}
//} //<- and uncomment this
using var_t = std::variant<myClasses::classA, myClasses::classB>;
int main()
{
std::set<var_t> myset;
myset.emplace(myClasses::classB(1));
myset.emplace(myClasses::classA(2));
return 0;
}
If you put the operator<s in the namespace myClasses it compiles fine.
Can someone explain to me, why my original attempt failed? If I just compare
myClasses::classA(1) < myClasses::classA(2), there's no need to put the operators in the myClasses namespace.
Thank you very much in advance.
This is an feature of argument dependent lookup (ADL), which you are relying on when the operator is search for from std::variant.
When searching for free functions, including overloaded operators, the compiler will only search in namespaces related to the arguments to the function in question.
There are more details in this cppreference article.
Where you call the comparison directly from your code, at the same namespace scope as the operator< declarations, "normal" (i.e. non-ADL) lookup can find the operator definition directly.

Look up overloaded operators in outer namespace in class design

How to let a wrapper class in a namespace know that in the outer/global namespace there may exist overloaded operators for the object that it wraps?
Note: I've heard of ADL, or Koenig lookup, but I met a real problem.
The Real Design Problem
I want to design a header-only library. Say I put everything in namespace my. The part related to this question can be simplified to something like a template wrapper item.
// my.hpp
#include <iostream>
namespace my
{
template<typename T>
struct item
{
T thing;
item(T t) : thing(t) {}
};
template<typename T>
std::ostream & operator<<(std::ostream & os, const item<T> & it)
{
os << it.thing;
return os;
}
}
With item what I want to achieve is that:
item<T> wraps a T object (with T object to be provided by user)
If operator<<(std::ostream &, const T &) is not defined in <iostream>, then I assume the user has overloaded operator<<(std::ostream &, const T &), and I want operator<<(std::ostream &, const item<T> &) to call it.
A Concrete User Example
Consider a set of user code that does so for T = std::vector<double>
// user.hpp
// #include guard omitted
#include <iostream>
#include <vector>
std::ostream & operator<<(std::ostream &, const std::vector<double> &);
and
// user.cpp
#include <iostream>
#include <vector>
std::ostream & operator<<(std::ostream & os, const std::vector<double> & v)
{
for (const auto & e : v)
os << e << " | ";
return os;
}
int main()
{
std::vector<double> vec = {3.14, 2.83};
std::cout << my::item<std::vector<double>>(vec);
}
Now if the user put
#include "user.hpp"
#include "my.hpp"
at the beginning of user.cpp, everything would be fine and g++ user.cpp would compile as expected.
However, if the user changed the order and put
#include "my.hpp"
#include "user.hpp"
the compiler would generate an error saying
my.hpp: In function 'std::ostream& my::operator<<(std::ostream&, const my::item<T>&)':
my.hpp:15:23: error: '::operator<<' has not been declared
Certainly I do not want the result to be dependent on the order of #include.
My question is: As a designer of namespace my and wrapper item<T>, what can I do to my and item<T> so that item<T> can correctly spot and call operator<<(std::ostream, const T &) if it is provided by user?
Thank you for your time!
Update: For your information, g++ --version returns
g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 7.3.0
After reading a question and answer on meta, and following suggestions from comments, I am moving some "updates" in my own question and posting them formally as a self-answer. I am doing so in the hope that this will help someone in future who happen to meet the same kind of problem. Thank you!
As #xskxzr correctly points out in the comment, there is something bad in the user code. To be specific,
it is bad to declare functions/operations whose parameters are all std entities as you cannot add such declarations into std to make use of ADL
In this case, the problem lies on the side of the user, not on the designer.
Now if the user made a change
// user.hpp
#include <iostream>
#include <vector>
// CHANGE: (privately) inherit from std::vector<double>, rather than overload directly
struct DoubleVector : private std::vector<double>
{
using std::vector<double>::vector;
friend
std::ostream & operator<<(std::ostream &, const DoubleVector &);
};
std::ostream & operator<<(std::ostream &, const DoubleVector &);
and
// user.cpp
#include "my.hpp"
#include "user.hpp"
#include <iostream>
#include <vector>
// CHANGE: use a user-defined DoubleVector class
std::ostream & operator<<(std::ostream & os, const DoubleVector & c)
{
for (const auto & e : c)
os << e << " | ";
return os;
}
int main()
{
DoubleVector vec = {3.14, 2.83};
std::cout << my::item<DoubleVector>(vec);
}
Then the user code would compile regardless of the order of #include "my.hpp" and #include "user.hpp".

std::sort() does not look for global operator<() defined

Consider a code below:
#include <algorithm>
#include <vector>
#include <string>
struct Foo
{
std::string str;
};
inline bool operator<(Foo const& lhs, Foo const& rhs)
{
return lhs.str > rhs.str;
}
int main()
{
std::vector<Foo> v = {{"abc"}, {"def"}};
std::sort(v.begin(), v.end());
}
The problem is that this works perfectly without any problems.
But in my production code:
std::sort(v.begin(), v.end());
Does NOT work; but using the code below DOES work:
std::sort(v.begin(), v.end(), &::operator<);
So what is happening here? I totally have no clue to this problem.
I'm suspecting that the ADL is doing something bad with namespaces defined in my production code.
You are not providing enough information. However, an educated guess would be that your actual code involves namespaces and you are experiencing an ordinary ADL (argument-dependent lookup) failure.
If the type stored in v is not a member of global namespace, then the compiler is not supposed to unconditionally search the entire global namespace for comparison operator.
The compiler will perform regular lookup for operator < from the point of definition of std::sort, which obviously will not find your global operator < (since std::sort is defined somewhere in one of the standard library files).
The compiler will also perform ADL in namespaces associated with the type being compared. You are not telling us much about that type, but I'd guess that that type is not defined in the global namespace. For this reason global namespace is not nominated for ADL and the operator is not found.
Here a simple example that won't compile for the very same reason
#include <iterator>
#include <algorithm>
namespace N {
struct S { int i = 0; };
}
bool operator <(const N::S &lhs, const N::S &rhs)
{
return lhs.i < rhs.i;
}
int main()
{
N::S a[10];
std::sort(std::begin(a), std::end(a));
}
The compiler is not supposed to find the global operator < even though it is right there in plain sight.

Print vector of vectors to ostream

Please consider the following code. I'm trying to output a vector of vectors to an ostream.
#include <iterator>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
template<typename T>
std::ostream &operator <<(std::ostream &os, const std::vector<T> &v) {
using namespace std;
copy(v.begin(), v.end(), ostream_iterator<T>(os, "\n"));
return os;
}
int main() {
using namespace std;
vector<string> v1;
cout << v1;
vector<vector<string> > v2;
cout << v2;
return 0;
}
The statement where I output a vector of strings works. The one where I output a vector of vectors of strings doesn't. I'm using g++ 4.7.0. I've tried w/ & w/o the -std=c++11 flag. In C++11 mode, it gives me this line in the half-page of errors.
error: cannot bind 'std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >::ostream_type {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'
I don't think I understand what it means. Could someone explain to me? I more or less know what an rvalue reference is, but I don't see why std::basic_ostream<char> wouldn't bind to std::basic_ostream<char>&&. Maybe I don't know it well enough. And is there a better way to do this?
Thanks in advance.
The error you're getting is a bit misleading. When I tried to compile your program I had to dig into the template vomit quite a bit, and I ended up with what I thought was going on:
error: no match for 'operator<<' in '*((std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >*)this)->std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >::_M_stream << __value'
Basically, when you called the copy algorithm, it used the output iterator, which used the << operator from within namespace std. Once there, lookup dictates that it try to find an overload for the template vector<> in the std namespace (because that's where IT resides).
So what you need to do is declare your stream operator for the vector template in namespace std. Surround your code with namespace std {} and see what happens...
It should be noted that what you're doing is basically modifying std::vector<> and adding behavior to it that wasn't there before. Doing this is non-standard, undefined, and can easily get in your way. You might consider other options.
I was wrong about this being a koenig lookup thing. It's not, the issue is name hiding similar to what occurs in classes here you declare an overload of something in a base (not an override).
The standard namespace declares several '<<' operators. These are basically functions named operator <<. In essence what you have is this:
void fun(int);
namespace Test {
void fun() { fun(3); }
}
int main() {
Test::fun();
}
Note that you can use fun(int) from the global namespace or any namespace that does not have any function named fun in it. You can't use it from the Test namespace.
This is why your use of operator << declared globally works fine from the global namespace but not from within the std namespace. The std namespace already has things named the same thing as the overload you're trying to provide and so that overload is hidden from all things within std. If you could put a using declaration there things would be different.
You need this utility library:
Pretty-print C++ STL containers
If you want to do this yourself (so as to teach yourself), then you need to define two overloads as:
For std::vector<T>:
template<typename T>
std::ostream &operator <<(std::ostream &os, const std::vector<T> &v) {
using namespace std;
copy(v.begin(), v.end(), ostream_iterator<T>(os, "\n"));
return os;
}
For std::vector<std::vector<T>>:
template<typename T>
std::ostream &operator <<(std::ostream &os, const std::vector<std::vector<T>> &v) {
using namespace std;
//NOTE: for some reason std::copy doesn't work here, so I use manual loop
//copy(v.begin(), v.end(), ostream_iterator<std::vector<T>>(os, "\n"));
for(size_t i = 0 ; i < v.size(); ++i)
os << v[i] << "\n";
return os;
}
If you have these overloads, then they together will handle these cases recursively:
std::vector<int> v;
std::vector<std::vector<int>> vv;
std::vector<std::vector<std::vector<int>>> vvv;
std::vector<std::vector<std::vector<std::vector<int>>>> vvvv;
std::cout << v << std::endl; //ok
std::cout << vv << std::endl; //ok
std::cout << vvv << std::endl; //ok
std::cout << vvvv << std::endl; //ok