Reputation: 1311
Hello insiders of the standard,
I want to read pairs of numbers from a file into a std::map and use code like the appended one. Now, it works but I have a couple of questions on how reliable it is.
My main problem is in the two lines marked with (*). Here the templates (i/o)stream_iterator<...> get instantiated. Within them code like (#)
std::pair<int, int> x;
some_istream >> x; // (##)
gets created where the line with (##) actually calls an operator of signature
std::istream & operator >> (std::istream & is, std::pair<int, int> & v);
Now ADL comes on place and decides where to look for the operator >>. It considers the current namespace and the namespaces of all arguments. Now since all arguments live in namespace std and the code (#) itself also lives in std (it gets instanciated within that namespace as the template is defined there), std is the only namespace considered at all. So I definately must make my operator available in that namespace somehow, don't I?
Unfortunatelly, I'm not allowed to put new symbols into the namespace std (I heared so). So I looked for the least invasive way to archive my goal anyway. I found I can just put my operator in my own namespace and announce it to the std namespace (see (**)) in the implementation file where I use it (that's where I use the templates (*)).
So here are my questions:
Am I "allowed" to do this? At least gcc does not add a new symbol to the std namespace.
Is it "safe" to do this? Having (**) only in the implementation file can't corrupt any code in other files, does it?
Is the end of the implementation file the "right" place to do it or do I need to move (**) to (***), i.e. before the use of the template that needs the operator? At least for gcc and vc it works. I suspect the code from template instanciation is virtually written to the end of the file after (**) so it works. Is this defined behaviour or just implementation defined?
Thanks,
imix
Code:
my_io.h
#include <iostream>
#include <map>
#include <tuple>
namespace my {
std::istream & operator >> (std::istream & is, std::pair<int, int> & v);
std::ostream & operator << (std::ostream & os, std::pair<int, int> const & v);
std::istream & operator >> (std::istream & is, std::map<int, int> & m);
std::ostream & operator << (std::ostream & os, std::map<int, int> const & m);
}
io.cpp
#include "my_io.h"
#include <algorithm>
#include <iterator>
namespace my {
using namespace std;
istream & operator >> (istream & is, pair<int, int> & v) {
return is >> v.first >> v.second;
}
ostream & operator << (ostream & os, pair<int, int> const & v) {
return os << v.first << ": " << v.second;
}
// (***)
istream & operator >> (istream & is, map<int, int> & m) {
using It = istream_iterator<pair<int, int>>;
auto it_begin = It(is); // (*)
auto it_end = It();
auto it_insert = inserter(m, m.begin());
copy(it_begin, it_end, it_insert);
return is;
}
ostream & operator << (ostream & os, map<int, int> const & m) {
auto it = ostream_iterator<pair<int, int>>(os, "\n"); // (*)
copy(m.begin(), m.end(), it);
return os;
}
}
namespace std { // (**)
using my::operator >>;
using my::operator <<;
}
main.cpp
#include "my_io.h"
#include <sstream>
#include <string>
using namespace std;
namespace my {
void work() {
string input = "1 2 3 4 5 6";
istringstream is(input);
map<int, int> m;
is >> m;
cout << m;
}
}
int main() {
my::work();
return 0;
}
EDIT: Solution (see Jonathan Walkley's answer)
The following wrapper type will solve the problem:
template <typename Key, typename T>
struct wrapped_pair {
std::pair<Key, T> x;
wrapped_pair(std::pair<Key const, T> x = std::pair<Key const, T>()) : x(x) { }
operator std::pair<Key const, T> () const { return x; }
};
I tried it with a wrapper type before, but I got the const
's in the implicit conversions wrong. Now, this is a working solution to my problem:
my_io.h:
#include <iostream>
#include <map>
std::istream & operator >> (std::istream & is, std::map<int, int> & m);
std::ostream & operator << (std::ostream & os, std::map<int, int> const & m);
my_io.cpp:
#include "my_io.h"
#include <algorithm>
#include <iterator>
using namespace std;
struct wrapped_pair {
pair<int, int> x;
wrapped_pair(pair<int const, int> x = pair<int const, int>()) : x(x) { }
operator pair<int const, int>() const { return x; }
};
istream & operator >> (istream & is, wrapped_pair & v) {
return is >> v.x.first >> v.x.second;
}
ostream & operator << (ostream & os, wrapped_pair const & v) {
return os << v.x.first << ": " << v.x.second;
}
istream & operator >> (istream & is, map<int, int> & m) {
using It = istream_iterator<wrapped_pair>;
copy(It(is), It(), inserter(m, m.begin()));
return is;
}
ostream & operator << (ostream & os, map<int, int> const & m) {
auto it = ostream_iterator<wrapped_pair>(os, "\n");
copy(m.begin(), m.end(), it);
return os;
}
main.cpp:
#include "my_io.h"
#include <sstream>
#include <string>
using namespace std;
int main() {
string input = "1 2 3 4 5 6";
istringstream is(input);
map<int, int> m;
is >> m;
cout << m;
return 0;
}
I know, I'm still hijacking the operators >>
and <<
for std::map
but I leave it here for simplicity.
Upvotes: 1
Views: 110
Reputation: 171491
It is undefined behaviour to add those declarations to namespace std
, and ADL won't find operators in namespace my
if none of the types are associated with that namespace.
A better solution is to define your own wrapper/tag type and read into that. You can overload operator>>
for your own type in your own namespace without problems.
std::pair<int, int> x;
some_istream >> my::IntPair{x};
The IntPair
type doesn't need to do anything fancy:
namespace my {
struct IntPair {
std::pair<int, int>& v;
};
inline std::istream&
operator>>(std::istream& in, IntPair&& ip)
{ return is >> ip.v.first >> ip.v.second; }
}
This is not undefined, and is also more "polite" because it doesn't hijack operator>>
for types you don't own. You didn't write std::pair<int, int>
so you shouldn't get to decide how stream extraction works for it. You did write IntPair
so you can make it do whatever you want.
You can generalize this to work for other types that you don't "own":
namespace my {
// write functions (not operators) to extract the types you care about:
inline std:::istream&
do_xtract(std::istream& in, std::pair<int, int>& v)
{ return is >> v.first >> v.second; }
inline std:::istream&
do_xtract(std::istream& in, std::map<int, int>& v)
{ /* ... */ }
// write a generic wrapper that can invoke those functions:
template<typename T>
struct Xtractor {
T& v;
}
template<typename T>
inline Xtractor<T>
xtract(T& t)
{ return { v }; }
template<typename T>
inline std:::istream&
operator>> (std::istream& in, Xtract<T>&& x)
{ return do_xtract(in, x); }
}
Now you can do:
std::map<int, int> m;
std::cin >> my::xtract(m);
A completely different solution would be to use std::map<my::WrappedInt, int>
where WrappedInt
is a trivial struct that holds an int
and has overloaded operators. Now you can define my::operator>>(std::istream&, std::pair<my::WrappedInt, int>&)
and my::operator>>(std::istream&, std::map<my::WrappedInt, int>&)
and they will be found by ADL, because my
is an associated namespace of those types.
Upvotes: 1