namespace/ADL 事物的解决方案的标准一致性
Standard conformity of a solution to a namespace/ADL thing
你好,标准内部人士,
我想从文件中读取成对的数字到 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 就位并决定在哪里寻找操作员 >>。它考虑当前命名空间和所有参数的命名空间。现在,由于所有参数都存在于命名空间 std 中,并且代码 (#) 本身也存在于 std 中(它在该命名空间中实例化,因为模板在那里定义),std 是唯一考虑的命名空间。所以我肯定必须以某种方式让我的运算符在那个命名空间中可用,不是吗?
不幸的是,我不允许将新符号放入命名空间 std(我听说过)。所以无论如何我都在寻找侵入性最小的方法来实现我的目标。我发现我可以将我的运算符放在我自己的命名空间中,并在我使用它的实现文件中将它声明到 std 命名空间(参见 (**))(那是我使用模板 (*) 的地方)。
所以这是我的问题:
我"allowed"要这样做吗?至少 gcc 不会在 std 命名空间中添加新的符号。
难道"safe"要这样做吗?仅在实现文件中使用 (**) 不会破坏其他文件中的任何代码,对吗?
执行文件的末尾是 "right" 执行它的地方还是我需要将 (**) 移动到 (***),即模板使用前需要操作员吗?至少对于 gcc 和 vc 它是有效的。我怀疑来自模板实例化的代码实际上是在 (**) 之后写入文件末尾的,所以它可以工作。这是已定义的行为还是仅定义了实现?
谢谢,
混音
代码:
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);
我的_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
劫持运算符 >>
和 <<
,但为了简单起见,我将其留在这里。
将这些声明添加到命名空间 std
是未定义的行为,如果 none 类型与该命名空间关联,ADL 将不会在命名空间 my
中找到运算符。
更好的解决方案是定义您自己的 wrapper/tag 类型并读入它。您可以在自己的命名空间中为自己的类型重载 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; }
}
这不是未定义的,而且更 "polite" 因为它不会劫持 operator>>
您不拥有的类型。你没有写 std::pair<int, int>
所以你不应该决定流提取是如何工作的。你确实写了 IntPair
所以你可以让它做任何你想做的事情。
您可以将其概括为适用于您不适用的其他类型 "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); }
}
现在你可以做:
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
是这些类型的关联命名空间。
你好,标准内部人士,
我想从文件中读取成对的数字到 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 就位并决定在哪里寻找操作员 >>。它考虑当前命名空间和所有参数的命名空间。现在,由于所有参数都存在于命名空间 std 中,并且代码 (#) 本身也存在于 std 中(它在该命名空间中实例化,因为模板在那里定义),std 是唯一考虑的命名空间。所以我肯定必须以某种方式让我的运算符在那个命名空间中可用,不是吗?
不幸的是,我不允许将新符号放入命名空间 std(我听说过)。所以无论如何我都在寻找侵入性最小的方法来实现我的目标。我发现我可以将我的运算符放在我自己的命名空间中,并在我使用它的实现文件中将它声明到 std 命名空间(参见 (**))(那是我使用模板 (*) 的地方)。
所以这是我的问题:
我"allowed"要这样做吗?至少 gcc 不会在 std 命名空间中添加新的符号。
难道"safe"要这样做吗?仅在实现文件中使用 (**) 不会破坏其他文件中的任何代码,对吗?
执行文件的末尾是 "right" 执行它的地方还是我需要将 (**) 移动到 (***),即模板使用前需要操作员吗?至少对于 gcc 和 vc 它是有效的。我怀疑来自模板实例化的代码实际上是在 (**) 之后写入文件末尾的,所以它可以工作。这是已定义的行为还是仅定义了实现?
谢谢,
混音
代码:
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);
我的_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
劫持运算符 >>
和 <<
,但为了简单起见,我将其留在这里。
将这些声明添加到命名空间 std
是未定义的行为,如果 none 类型与该命名空间关联,ADL 将不会在命名空间 my
中找到运算符。
更好的解决方案是定义您自己的 wrapper/tag 类型并读入它。您可以在自己的命名空间中为自己的类型重载 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; }
}
这不是未定义的,而且更 "polite" 因为它不会劫持 operator>>
您不拥有的类型。你没有写 std::pair<int, int>
所以你不应该决定流提取是如何工作的。你确实写了 IntPair
所以你可以让它做任何你想做的事情。
您可以将其概括为适用于您不适用的其他类型 "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); }
}
现在你可以做:
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
是这些类型的关联命名空间。