您好标准的内部人员,
我想从文件中读取一对数字到std :: map,并使用附加的代码。现在,它有效,但我对它的可靠性有几个问题。
我的主要问题是标有(*)的两行。这里模板(i / o)stream_iterator< ...>得到实例化。在其中的代码如(#)
std::pair<int, int> x;
some_istream >> x; // (##)
在带有(##)的行实际调用签名运算符
的位置创建std::istream & operator >> (std::istream & is, std::pair<int, int> & v);
现在ADL到位并决定在哪里寻找运营商&gt;&gt;。它考虑当前命名空间和所有参数的命名空间。现在因为所有参数都存在于命名空间std中,并且代码(#)本身也存在于std中(它在该命名空间中得到实例化,因为模板在那里定义),std是唯一考虑的命名空间。所以我肯定必须以某种方式让我的操作符在该命名空间中可用,不是吗?
不幸的是,我不允许将新符号放入名称空间std(我听到了)。因此,无论如何,我寻找最不具侵略性的方式来存档我的目标。我发现我可以将我的操作符放在我自己的命名空间中,并将其公布到我使用它的实现文件中的std命名空间(参见(**))(这就是我使用模板(*)的地方)。
所以这是我的问题:
我“允许”这样做吗?至少gcc不会向std命名空间添加新符号。
这样做“安全”吗?只有(**)在实现文件中不能破坏其他文件中的任何代码,是吗?
实施文件的结尾是“正确”的地方,还是我需要移动(**)到(***),即在使用需要操作员的模板之前?至少对于gcc和vc来说它是有效的。我怀疑来自模板实例的代码实际上是在(**)之后写到文件的末尾,所以它可以正常工作。是这个定义的行为还是仅定义了实现?
谢谢,
IMIX
代码:
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;
}
编辑: 解决方案(请参阅Jonathan Walkley的回答)
以下包装器类型将解决问题:
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; }
};
之前我尝试过使用包装器类型,但是隐式转换中的const
错了。现在,这是解决我问题的有效方法:
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;
}
我知道,我仍在为>>
劫持运营商<<
和std::map
,但为了简单起见,我将其留在此处。
答案 0 :(得分:1)
将这些声明添加到命名空间std
是未定义的行为,如果没有任何类型与该命名空间关联,则ADL将无法在命名空间my
中找到运算符。
更好的解决方案是定义自己的包装器/标签类型并阅读它。您可以在自己的命名空间中为自己的类型重载operator>>
而不会出现问题。
std::pair<int, int> x;
some_istream >> my::IntPair{x};
IntPair
类型不需要做任何花哨的事情:
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; }
}
这不是未定义的,也更“礼貌”,因为它不会为你不拥有的类型劫持operator>>
。你没有写std::pair<int, int>
所以你不应该决定流提取是如何工作的。你确实写了IntPair
所以你可以做任何你想做的事。
您可以将其概括为适合您不“拥有”的其他类型:
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); }
}
现在你可以做到:
std::map<int, int> m;
std::cin >> my::xtract(m);
完全不同的解决方案是使用std::map<my::WrappedInt, int>
,其中WrappedInt
是一个包含int
且重载运算符的简单结构。现在您可以定义my::operator>>(std::istream&, std::pair<my::WrappedInt, int>&)
和my::operator>>(std::istream&, std::map<my::WrappedInt, int>&)
,它们将由ADL找到,因为my
是这些类型的关联命名空间。