【问题标题】:std::experimental::ostream_joiner and std::pairstd::experimental::ostream_joiner 和 std::pair
【发布时间】:2017-09-27 18:17:55
【问题描述】:

在 c++17/g++7 中,终于有了久违的 ostream_joiner。它可以正确输出到 ostream,用中缀分隔符分隔集合元素。

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>
#include <vector>
#include <string>

using string = std::string;
#if 1
struct pair {
    string first;
    string second;
};
#else
using pair = std::pair<string,string>;
#endif


std::ostream& operator<<(std::ostream& lhs, const pair &p) {
    return lhs << p.first << "=" << p.second;
}

int main()
{
    std::vector<pair> pairs = {{"foo", "bar"}, {"baz", "42"}};
    std::copy(std::begin(pairs),
          std::end(pairs),
          std::experimental::make_ostream_joiner(std::cout, ", "));
}

虽然代码片段成功编译并输出...

foo=bar, baz=42

... 将 sn-p 中的 #if 1 更改为 #if 0 会使编译器抱怨缺少正确的移位运算符:

main.cpp:29:70:   required from here
/usr/local/include/c++/7.2.0/experimental/iterator:88:10: error: no match for 
'operator<<' (operand types are 
'std::experimental::fundamentals_v2::ostream_joiner<const char*, char, 
std::char_traits<char> >::ostream_type {aka std::basic_ostream<char>}' and 
'const std::pair<std::__cxx11::basic_string<char>, 
std::__cxx11::basic_string<char> >')
  *_M_out << __value;

有人知道为什么吗?

更新

巴里给出了这个问题的正确答案。然而它并没有解决问题,并且运行手动循环并不是重用现有的stl代码,所以问题延伸到:

是否可以在不污染std命名空间的情况下让流算子工作?

【问题讨论】:

标签: c++ operator-overloading c++17


【解决方案1】:

ostream_joiner 的实现内部的某个地方,会有类似的尝试:

os << value;

其中osstd::basic_ostream,值是您的pair 类型。为了确定对该operator&lt;&lt; 调用执行什么操作,我们查找在此模板定义点可见的所有重载operator&lt;&lt;() 以及关联命名空间 中的重载。参数(这称为argument-dependent lookup)。

当你使用你的struct pair时,pair的关联命名空间是::,所以ADL会找到你的::operator&lt;&lt;(std::ostream&amp;, pair const&amp;)。这个重载有效,被选中,一切皆大欢喜。

当您使用std::pair 时,pair 的关联命名空间是std,并且没有可以找到采用std::pairoperator&lt;&lt;()。因此错误。


您可以改为在自己的命名空间中创建自己的类型,您可以为其添加重载的operator&lt;&lt;,这可以完全是您自己的类型(问题中的方式),或者您可以继承@987654339 中的类型@:

struct pair : std::pair<string,string> {
    using std::pair<string,string>::pair;
};
std::ostream& operator<<(std::ostream&, my_pair const& ) {...}

或者,您不能使用make_ostream_joiner。可以替换这个:

std::copy(std::begin(pairs),
      std::end(pairs),
      std::experimental::make_ostream_joiner(std::cout, ", "));

用这个:

const char* delim = "";
for (auto const& pair : pairs) {
    std::cout << delim << pair; // now, our point of definition does include
                                // our operator<<() declaration, we don't need ADL
    delim = ", ";
}

【讨论】:

  • 谢谢,愚蠢的我 - 接受。但是:我们是需要通过插入流操作符来污染命名空间,还是在这里放弃?
  • @argonaut6x 将新函数添加到命名空间 std 是 UB,所以不要这样做。
  • 所以我们要放弃了?没有其他方法可以使运算符
  • @argonaut6x 不要使用std::pair?使用 for 循环代替 copymake_ostream_joiner?
  • @argonaut6x 这是两个选项。不知道您为什么认为这会“适得其反”。
【解决方案2】:

是否可以在不污染std命名空间的情况下让流算子工作?

如果您愿意用transform 替换您的copy,那么可以 - 使用取自my answer hereput_invocation,您可以使用:

std::transform(
    std::begin(pairs), std::end(pairs),
    std::experimental::make_ostream_joiner(std::cout, ", "),
    [](auto& p) {
        return put_invocation([&p = p](auto& os) {
            // adl works just fine here
            return os << p;
            // or `os << p.first << "=" << p.second;`
        });
    }
);

这应该优化为与注入 std 命名空间的版本完全相同的代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-16
    • 2011-10-29
    • 1970-01-01
    • 1970-01-01
    • 2010-10-12
    • 2013-08-18
    • 2011-01-01
    • 2021-09-03
    相关资源
    最近更新 更多