如何在基于范围的for循环中很好地解压缩unordered_map元素?

时间:2015-07-24 13:53:51

标签: c++ for-loop

考虑以下代码:

#include <tuple>
#include <string>
#include <iostream>
#include <unordered_map>    
int main()
{
    std::unordered_map<int,std::string> map {{5,"five"},{1,"one"},{-7,"minus seven"}};
    int x;
    std::string s;
    for(std::tie(x,s) : map)
        std::cout << x << "->" << s << "\n";
}

这里有for的行我从clang ++ - 3.6中得到错误:

  

test.cpp:14:23:错误:对于范围声明必须声明一个变量

这似乎意味着,如果std::tie解除pair valfor(auto val: map),如果我做{{1}},我就会失败。

但是我真的很想在循环体中没有任何额外的线条的情况下自动解开对。是否还有任何优雅的方法来实现这一目标?

5 个答案:

答案 0 :(得分:2)

一些想法。第一个需要额外的一行预采样来构造一对迭代器,并使用while循环:

template <typename I, typename... Ts>
bool for_tie(std::pair<I,I>& its, Ts&&... parts)
{
    if (its.first == its.second) return false;
    std::tie(std::forward<Ts>(parts)...) = *its.first++;
    return true;
}

int main()
{
    std::unordered_map<int,std::string> map {{5,"five"},{1,"one"},{-7,"minus seven"}};
    int x;
    std::string s;
    auto map_its = std::make_pair(begin(map), end(map));
    while(for_tie(map_its, x, s))
        std::cout << x << "->" << s << "\n";
}

DEMO

下一个API有一个类似于标准库算法的API,这里使用的是lambda:

#include <experimental/tuple>

template <typename I, typename F>
void for_tie(I it, I end, F func)
{
    for (; it != end; ++it) std::experimental::apply(func, *it);
}

int main()
{
    std::unordered_map<int,std::string> map {{5,"five"},{1,"one"},{-7,"minus seven"}};
    for_tie(begin(map), end(map), [](int x, const std::string &s) {
        std::cout << x << "->" << s << "\n";
    });
}

DEMO

如果您无法访问库基础知识TS的实现,那么

std::experimental::apply很容易实现。

答案 1 :(得分:2)

以下是使用特殊模板类map_unpacker的想法,它不应该在任何意义上完成,只是为了表明这个想法:

#include <tuple>
#include <string>
#include <iostream>
#include <unordered_map>
#include <tuple>
#include <algorithm>

template<typename T1, typename T2>
struct map_unpacker {
    typedef typename std::unordered_map<T1,T2> convert_map;
    typedef typename convert_map::const_iterator convert_iterator;
    typedef typename convert_map::value_type pair;

    struct iterator : convert_iterator {
        iterator( const map_unpacker &u, convert_iterator it ) : unpack( u ), convert_iterator( it ) {}

        pair operator*() { auto &&p = convert_iterator::operator*(); unpack.ref1 = p.first; unpack.ref2 = p.second; return p; }

        const map_unpacker &unpack;
    };

    map_unpacker( const std::unordered_map<T1,T2> &m, T1 &r1, T2 &r2 ) : map( m ), ref1( r1 ), ref2( r2 ) {}

    iterator begin() const { return iterator( *this, map.begin() ); }
    iterator end() const { return iterator( *this, map.end() ); }

private:
    const std::unordered_map<T1,T2> &map;
    T1 &ref1;
    T2 &ref2;
};

int main()
{
    std::unordered_map<int,std::string> map {{5,"five"},{1,"one"},{-7,"minus seven"}};
    int x;
    std::string s;
    for( auto &&v : map_unpacker<int,std::string>( map, x, s ) ) {
        std::cout << "x==" << x << " s==" << s << std::endl;
    }
}

答案 2 :(得分:1)

好的,既然你喜欢语法糖,可能不太关心性能,那么我想出了这个想法:

构建自己的类,它包装地图并返回自己的迭代器,每次迭代器递增时,迭代器都会保存x和s中的值。

以下代码可能不是我写过的最好的代码,但它可以解决您的问题,满足您的限制,并可以由您进一步改进。

class mai_tie
{
public:
    class iterator
    {
    public:
        iterator(std::unordered_map<int, std::string>::iterator& pos, std::unordered_map<int, std::string>::iterator& end, int& x, std::string& s)
        : _pos(pos), _end(end), _x(&x), _s(&s)
        {
            *_x = pos->first;
            *_s = _pos->second;
        }

        iterator(std::unordered_map<int, std::string>::iterator pos) : _pos(pos), _end(pos)
        {}

        bool operator==(const iterator& rhs)
        { return _pos == rhs._pos; }

        bool operator!=(const iterator& rhs)
        { return _pos != rhs._pos; }

        void operator++()
        {
            ++_pos;
            if (_pos != _end && _x && _s)
            { 
                *_x = _pos->first;
                *_s = _pos->second;
            }
        }

        std::pair<int, std::string> operator*()
        {
            return std::make_pair(_pos->first, _pos->second);
        }

    private:
        std::unordered_map<int, std::string>::iterator _pos;
        const std::unordered_map<int, std::string>::iterator _end;
        int* _x = nullptr;
        std::string* _s = nullptr;
    };

    mai_tie(std::unordered_map<int, std::string>& map, int& x, std::string& s) : _map(map), _x(x), _s(s) {};

    iterator begin()
    {
        return iterator(_map.begin(), _map.end(), _x, _s);
    }

    iterator end()
    {
        return iterator(_map.end());
    }

private:
    std::unordered_map<int, std::string>& _map;
    int& _x;
    std::string& _s;
};

你会像这样使用它:

std::unordered_map<int, std::string> map{ { 5, "five" }, { 1, "one" }, { -7, "minus seven" } };
int x;
std::string s;
for (auto z : mai_tie(map, x, s))
    std::cout << x << "->" << s << "\n";

请原谅我这堂课的名字,今天感觉“好笑”。

个人建议:不要使用这门课。没有人会知道代码中的F是什么。编写规则,简单的代码。保存一行是不值得的。在您之后的下一个编码器将会感恩(从经验来讲,已经调试了很多代码,人们制作了这样的“聪明”的东西,以节省几行。)

答案 3 :(得分:1)

如今,最好的方法是使用C ++ 17中出现的structured bindings,就像下面的代码一样。

#include <tuple>
#include <string>
#include <iostream>
#include <unordered_map>    
int main()
{
    std::unordered_map<int,std::string> map {{5,"five"},{1,"one"},{-7,"minus seven"}};
    for(const auto& [x,s] : map)
        std::cout << x << " -> " << s << "\n";
}

答案 4 :(得分:0)

您可以按如下方式更改for循环:

for(const auto& ele: map)
  std::cout<< ele.first<<"->"<<ele.second<<"\n";

修改

for(const auto& x : map)
{
    std::tie(y,s)=x;
    std::cout << y << "->" << s << "\n";
}