Re: ostream_iterator with map pairs
Am 25.07.2014 um 17:18 schrieb Richard:
I must be doing something wrong, but I expected this to compile and work:
Yes, you are doing something wrong ;-)
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
std::ostream& operator<<(
std::ostream& out,
const std::pair<size_t, size_t>& rhs)
{
return out << rhs.first << ", " << rhs.second;
}
My general recommendation is the following: Never provide a function or
operator in a namespace that does not belong to the associated
namespaces of its arguments, if this function/operator is supposed to be
found by argument-depending lookup (ADL, also known as "Koenig lookup",
see http://en.wikipedia.org/wiki/Argument-dependent_name_lookup). What
you did here is to provide operator<< in the global namespace, but it's
argument types (std::ostream, std::pair<>) are defined in namespace std.
See below why this recommendation should be followed.
int main()
{
std::map<size_t, size_t> m;
for (size_t i = 0; i < 10; ++i)
{
m[i] = 2U*i;
}
std::copy(m.begin(), m.end(),
std::ostream_iterator<const std::pair<size_t, size_t>>(std::cout,
"\n"));
return 0;
}
When you use std::ostream_iterator's operator* (This is instantiated
within std::copy), operator<< is invoked by an unqualified call. This
unqualified call has the effect that unqualified name lookup happens and
as a consequence of this, the compiler searches for the name
"operator<<". beginning from the lexical location where the operator<<
call is found "upwards" (presumably starting somewhere in header
<iterator>) starting in the current namespace and all the namespaces
that include that namespace (including the global namespace, btw.) and -
as a second route - it performs a second phase of this lookup the
compiler searches in the so-called associated namespaces of the argument
types occurring in this call. These arguments types are std::ostream and
std::pair<std::size_t, std::size_t> here.
In the presented example, the first phase of the search fails, because
at the point where #include <iterator> exists, there is no corresponding
operator<< for these argument types in any namespace. Note that your
declaration of operator<< is provided lexically *after* the point where
the call of operator<< happens somewhere in some of the library headers.
The second phase of the search would also consider locations that
*follow* the actual function call, but only within the associated
namespaces. But the compiler doesn't succeed to find a matching
operator<< in namespace std, therefore the overall search fails and the
compiler badly chokes at you.
The first possible attempt to fix that problem is to attempt to make
phase 1 of the search successful. This route is a waggly one (I explain
in a second why), but if you would try to do that, the rationale for
doing so would be as follows: You could argue, "OK, if I need to provide
a declaration *before* the invocation of operator<<, I can try to do
that by the following arrangement:
#include <stddef.h> // Needed for size_t
#include <utility> // Needed for std::pair
#include <iosfwd> // Needed for std::ostream
std::ostream& operator<<(
std::ostream& out,
const std::pair<size_t, size_t>& rhs);
// You cannot assume that you can define this operator here, because
// <iosfwd> only provides a non-defining declaration of std::ostream
// and you cannot assume that any other operator<< overloads are
// declared at this point needed for the implementation
#include <algorithm>
#include <iostream>
#include <iterator> // Presumably here is the call of operator<<
#include <map>
// Ok, now define operator<<, because the other necessary declarations
// are available here:
std::ostream& operator<<(
std::ostream& out,
const std::pair<size_t, size_t>& rhs)
{
return out << rhs.first << ", " << rhs.second;
}
[..]
Using the clang compiler & library from this location
http://melpon.org/wandbox/
makes your overall example code compile and run successfully. Let me
warn you immediately that this approach is not a reliable one and I
don't recommend it! The purpose of this example was to explain the
different mechanisms of the name lookup.
The reason for that not being reliable idiom is that library headers are
free to include any other library headers. For example, the above code
still leads a compiler error using gcc 4.10 (use the same online web
page that I referred to above), presumably because header utility
already contains (via some internal header inclusion) the definition of
std::ostream_iterator's operator*. Besides the problem related to
unwarranted assumptions about library internals there is another one,
probably even worse: The approach also depends on the fragile
assumption, that there exists no other header inclusion *before* this
code part from above:
#include <stddef.h> // Needed for size_t
#include <utility> // Needed for std::pair
#include <iosfwd> // Needed for std::ostream
std::ostream& operator<<(
std::ostream& out,
const std::pair<size_t, size_t>& rhs);
In a typical multi-header inclusion structure of different and complex
inclusion hierarchies you have no good way to ensure this "first
declaration" guarantee.
Now the second possible attempt to fix this problem would be to inject
into the second phase of the name lookup process (Remember: The
associated namespace of the argument types of the function call are
considered). You could feel attempted to rewrite your code as follows:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
namespace std {
std::ostream& operator<<(
std::ostream& out,
const std::pair<size_t, size_t>& rhs)
{
return out << rhs.first << ", " << rhs.second;
}
}
[..]
This should compile and run, because now we have added a declaration of
operator<< in the associated namespace std and it will be found even
though this declaration follows the point of the invocation in the
second phase of the search, because it is still in scope at the point
where the template instantiation of std::ostream_iterator's operator*
happens (within your main() definition).
Note that this approach satisfies the namespace criteria that I
mentioned in my second sentence of this reply. Nonetheless I'm not
recommending to use this idiom in other than non-serious fun code. I'm
opposed to this approach, because
a) formally it is undefined behaviour to add declarations to namespace std
b) it violates the principle that non-one except the "owner" of a
namespace should add declarations to some given namespace unless this is
explicitly allowed.
In addition, the rationale for bullet (b) is that not only you, but
anyone else could have the same "brilliant" idea to add some
operator<<(std::ostream&t, std::pair<>);
to namespace std - lookup ambiguity would a very likely outcome of such
a scenario.
My personal favorite solution for this problem is a third possible
attempt: The idea also takes advantage of injecting into the second
phase of the name lookup, but now we are doing this by providing our own
namespace and we behave as "good citizens" relative to the Standard
Library. This requires a little helper type. To make this type more
reusable, I'm defining it as a template to make the approach work for
arbitrary types:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
namespace my_lib {
template <typename T>
class io_wrap {
public:
const T& t;
// Implicit conversion intended:
io_wrap(const T& t) : t(t) {}
};
// Now overload operator<< for any type you like:
template <class Ch, class Tr, class T1, class T2>
std::basic_ostream<Ch, Tr>& operator<<(std::basic_ostream<Ch, Tr>& out,
io_wrap<std::pair<T1, T2>> rhs)
{
return out << rhs.t.first << ", " << rhs.t.second;
}
}
int main()
{
typedef std::map<size_t, size_t> map_type;
map_type m;
for (size_t i = 0; i < 10; ++i)
{
m[i] = 2U*i;
}
std::copy(m.begin(), m.end(),
std::ostream_iterator<my_lib::io_wrap<map_type::value_type>>(std::cout,
"\n"));
}
HTH & Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]