【问题标题】:std::copy to std::cout for std::pairstd::copy 到 std::cout 的 std::pair
【发布时间】:2010-10-12 15:44:12
【问题描述】:

我有下一个代码:

#include <iostream>
#include <algorithm>
#include <map>
#include <iterator>

//namespace std
//{

std::ostream& operator << ( std::ostream& out, 
                const std::pair< size_t, size_t >& rhs )
{
    out << rhs.first << ", " << rhs.second;
    return out;
}
//}

int main() 
{

    std::map < size_t, size_t > some_map;

    // fill  some_map with random values
    for ( size_t i = 0; i < 10; ++i )
    {
        some_map[ rand() % 10 ] = rand() % 100;
    }

    // now I want to output this map
    std::copy( 
        some_map.begin(), 
        some_map.end(), 
        std::ostream_iterator< 
              std::pair< size_t, size_t > >( std::cout, "\n" ) );

    return 0;
}

在这段代码中,我只想将地图复制到输出流。为此,我需要定义运算符 因为 std::cout、std::pair 和 std::copy 调用了我的操作符

快速解决方案 - 将我的 oerator

您知道这个问题的哪些解决方案或解决方法?

【问题讨论】:

标签: c++ stl namespaces operator-overloading


【解决方案1】:

这是一个与 std::copystd::ostream_iterator 一起使用的适配器,用于 std::pair 类型。您最终会持有一个额外的引用,但编译器优化可能会处理它。顺便说一句,std::pairstd::map::value_type 的第一个类型将是 const

template <typename pair_type>
class pair_adaptor
{
public:
    const pair_type &m;
    pair_adaptor(const pair_type &a) : m(a) {}

    friend std::ostream &operator << (std::ostream &out, 
        const pair_adaptor <pair_type> &d)
    {
        const pair_type &m = d.m;
        return out << m.first << " => " << m.second;
    }
};

typedef std::map<size_t, size_t>::value_type value_type;

std::copy (mymap.begin(), mymap.end(),
    std::ostream_iterator < 
        pair_adaptor <value_type> > (std::cout, "\n"));

std::copy (mymap.begin(), mymap.end(),
    std::ostream_iterator < 
        pair_adaptor <
            std::pair<const size_t, size_t>>> (std::cout, "\n"));

【讨论】:

  • 其他人建议 std::for_each 使用 lambda 函数,std::transform 使用其他助手,但问题是在 std::copystd:ostream_iterator 的上下文中找到解决方案。
【解决方案2】:
 for_each(some_map.begin(), some_map.end(), [](const std::map < size_t, size_t >::value_type &ite){
             cout<<ite.first<<" "<<ite.second<<endl;

}); 

--- C++11 没问题

【讨论】:

    【解决方案3】:
        for (const auto& your_pair : your_container)
            your_stream << "[" << your_pair.first << "," << your_pair.second << "]" << endl;
    

    更简单通用!

    【讨论】:

      【解决方案4】:

      只是路过,但这对我有用,所以对其他人也可以(删减版):

      template<typename First, typename Second>
      struct first_of {
          First& operator()(std::pair<First, Second>& v) const {
              return v.first;
          }
      };
      

      给出的用例:

      transform (v.begin (), v.end (), 
                 ostream_iterator<int>(cout, "\n"), first_of<int, string> ());
      

      【讨论】:

        【解决方案5】:

        我找到了一种新的优雅方法来解决这个问题。
        阅读答案时,我有很多有趣的想法:

        • 包装迭代器,用于将 std::pair 转换为 std::string;
        • 包装 std::pair,以便有机会重载运算符
        • 使用通常的 std::for_each 和打印函子;
        • 使用 std::for_each 和 boost::labda - 看起来不错,除了访问 std::pair::first 和 std::pair::second 成员;

        我想我将来会使用所有这些想法来解决其他不同的问题。
        但是对于这种情况,我理解我可以将我的问题表述为“将地图的数据转换为字符串并将它们写入输出流”而不是“将地图的数据复制到输出流”。我的解决方案如下:

        namespace
        {
        std::string toString( const std::pair< size_t, size_t >& data)
        {
            std::ostringstream str;
            str << data.first << ", " << data.second;
            return str.str();
        }
        } // namespace anonymous
        
        std::transform( 
            some_map.begin(), 
            some_map.end(), 
            std::ostream_iterator< std::string >( std::cout, "\n" ),
            toString );
        

        我认为这种方法比其他方法最简短,最具表现力。

        【讨论】:

        • Michael 的解决方案有意避免使用 toString,因此它不会与其他 toString 定义发生冲突。此外,在多次使用后,他最终会比你的短(转换需要一个额外的参数)。因此,您声称比这里的最佳答案更短且更具表现力的说法是错误的。
        【解决方案6】:

        [我宁愿删除这个答案,但我先不说,以防有人觉得讨论很有趣。]

        既然它是对 std 库的合理扩展,我就把它放在 std 命名空间中,特别是如果这是一次性的事情。如果其他人在其他地方做同样的事情,您可以将其声明为静态以防止它导致链接器错误。

        想到的另一个解决方案是为 std::pair 创建一个包装器:

        template<class A, class B>
        struct pairWrapper {
          const std::pair<A,B> & x;
          pairWrapper(const std::pair<A,B> & x) : x(x) {}
        }
        
        template<class A,class B>
        std::ostream & operator<<(std::ostream & stream, const pairWrapper<A,B> & pw) { ... }
        

        【讨论】:

        • +1,非常好——转换构造函数会在需要时自动将 pair 转换为 pairWrapper。但是请将 ostream& 参数添加到您的模板化操作符
        • 错误,不允许。命名空间 std 用于编译器提供的类、模板和函数。您不能添加重载。
        • 您的 pairWrapper 可能还需要 operator=,因为 std::ostream_iterator 需要它,并且由于 const 成员,编译器无法为您生成它。
        • @Serge:你确定吗?因为那意味着它根本无法打印不可变对象。也许你的意思是复制构造函数?
        • @j_random_hacker:我什至没有想过自动转换!
        【解决方案7】:

        使用 Boost Lambda,您可以尝试这样的事情。我拥有的Boost Lambda版本,这个实际上不起作用,我稍后会测试并修复。

        #include <boost/lambda/lambda.hpp>
        #include <boost/lambda/bind.hpp>
        
        using namespace boost::lambda;
        
        std::for_each( some_map.begin(), some_map.end(), 
                       std::cout << bind( &std::map<size_t,size_t>::value_type::first, _1 )
                                 << ","
                                 << bind( &std::map<size_t,size_t>::value_type::second, _1 ) );
        

        【讨论】:

          【解决方案8】:

          没有标准的方法来计算std::pair,因为,你想要的打印方式可能与下一个人想要的方式不同。这是自定义仿函数或 lambda 函数的一个很好的用例。然后,您可以将其作为参数传递给 std::for_each 以完成这项工作。

          typedef std::map<size_t, size_t> MyMap;
          
          template <class T>
          struct PrintMyMap : public std::unary_function<T, void>
          {
              std::ostream& os;
              PrintMyMap(std::ostream& strm) : os(strm) {}
          
              void operator()(const T& elem) const
              {
                  os << elem.first << ", " << elem.second << "\n";
              }
          }
          

          从你的代码中调用这个函子:

          std::for_each(some_map.begin(),
                        some_map.end(),
                        PrintMyMap<MyMap::value_type>(std::cout));
          

          【讨论】:

            【解决方案9】:

            你想要的是一个转换迭代器。这种迭代器包装了另一个迭代器,转发所有定位方法如operator++和operator==,但重新定义了operator*和operator->。

            速写:

            template <typename ITER> 
            struct transformingIterator : private ITER {
                transformingIterator(ITER const& base) : ITER(base) {}
                transformingIterator& operator++() { ITER::operator++(); return *this; }
                std::string operator*() const
                {
                    ITER::value_type const& v = ITER::operator*();
                    return "[" + v->first +", " + v->second + "]";
                }
            ...
            

            【讨论】:

            • 谢谢。创建迭代器包装器的好主意,这个想法可以推广并用于解决其他问题。
            • 如果你想要一些通用的东西,一个明显的步骤是将转换存储在适当的 boost::function 中。当然,对于新的 value_type,您需要一个额外的模板参数。
            【解决方案10】:

            我只想指出,根据 C++ 标准,向 std:: 命名空间添加内容是非法的(请参阅第 17.4.3.1 节)。

            【讨论】:

            • 一个例外:您可以为std::swap 添加重载。
            • @konrad go 你有参考吗?
            • Herb Sutter 在他的一位“本周大师”中建议在每个文件的开头“使用 std::swap”,以便标准交换和用户定义的交换使用相同的语法。
            • 我认为 @david 与向 std:: 命名空间添加重载不太一样?
            • @Neil:抱歉,没有标准参考。 Scott Meyers 在 Eff C++ 中关于实现异常安全swap 的项目中这么说。由于搬家,我所有的书都被打包了,所以我无法查找确切的段落。 :-(
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-10-29
            • 1970-01-01
            • 1970-01-01
            • 2013-08-18
            • 2011-01-01
            • 1970-01-01
            • 2018-07-09
            相关资源
            最近更新 更多