【问题标题】:Standard conformity of a solution to a namespace/ADL thing命名空间/ADL 事物的解决方案的标准一致性
【发布时间】:2016-02-12 10:46:51
【问题描述】:

标准内部人员您好,

我想将文件中的数字对读入 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 命名空间(参见 (**))(这是我使用模板 (*) 的地方)。

所以这是我的问题:

  • 我是否“被允许”这样做?至少 gcc 不会向 std 命名空间添加新符号。

  • 这样做是否“安全”?仅在实现文件中使用 (**) 不会损坏其他文件中的任何代码,是吗?

  • 实现文件的末尾是执行它的“正确”位置还是我需要将 (&ast;&ast;) 移动到 (&ast;&ast;&ast;),即在使用模板之前那需要运营商吗?至少对于 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;
}

我知道,我仍在劫持运营商 &gt;&gt;&lt;&lt; 以获得 std::map,但为了简单起见,我将其留在这里。

【问题讨论】:

  • 您在 my 命名空间中定义了运算符,并从 my 命名空间中使用它们(work 函数在该命名空间中),并且您自己说“[i] t 考虑当前命名空间...”在my::work 函数中,当前命名空间是my,与重载运算符的命名空间相同,因此它无需在std 命名空间中添加任何内容即可工作。跨度>
  • @JoachimPileborg:它起作用了,没错。但它在标有 ( * ) 的行中不起作用,其中来自 std 的模板被实例化。尝试删除标记(**)的块,你会看到。

标签: c++ templates namespaces std argument-dependent-lookup


【解决方案1】:

将这些声明添加到命名空间std 是未定义的行为,如果没有任何类型与该命名空间相关联,ADL 将不会在命名空间my 中找到运算符。

更好的解决方案是定义您自己的包装器/标签类型并阅读它。您可以在自己的命名空间中为您自己的类型重载 operator&gt;&gt; 而不会出现问题。

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&gt;&gt; 用于您不拥有的类型。你没有写std::pair&lt;int, int&gt;,所以你不应该决定流提取是如何工作的。你确实写了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&lt;my::WrappedInt, int&gt;,其中WrappedInt 是一个简单的结构,它包含一个int 并具有重载的运算符。现在您可以定义my::operator&gt;&gt;(std::istream&amp;, std::pair&lt;my::WrappedInt, int&gt;&amp;)my::operator&gt;&gt;(std::istream&amp;, std::map&lt;my::WrappedInt, int&gt;&amp;),它们将被ADL 找到,因为my 是这些类型的关联命名空间。

【讨论】:

  • 哦,该死的。我首先使用包装器类型进行了尝试,但没有成功。我弄错了密钥,它是 const!,即 mapvalue_typepair&lt;Key const, T&gt;。我修正了我的问题,以便进一步解释。
猜你喜欢
  • 2010-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-24
  • 1970-01-01
  • 1970-01-01
  • 2011-12-24
  • 1970-01-01
相关资源
最近更新 更多