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 命名空间(参见 (**))(那是我使用模板 (*) 的地方)。

所以这是我的问题:

谢谢,

混音


代码:

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 是这些类型的关联命名空间。