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

How to nicely unpack unordered_map element in range-based for loop?

本文关键字:很好 unordered 元素 map 循环 for 于范围 范围      更新时间:2023-10-16

考虑以下代码:

#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: error: for range声明必须声明一个变量

这似乎意味着,如果我打开for(auto val: map), val将是std::tie的解包,那么CC_2就没有运气了。

但是我真的想在循环体中没有任何额外的行就自动解包这对。还有什么优雅的方法可以做到这一点吗?

目前最好的方法是使用c++ 17中出现的结构化绑定,如下面的代码所示。

#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";
}

几个想法。第一个示例需要额外的一行示例来构造一对迭代器,并使用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";
}

下一个有一个类似于标准库算法的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";
    });
}

std::experimental::apply是很容易实现自己,如果你没有访问库基础TS的实现。

这个想法是,使用特殊的模板类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;
    }
}

好的,既然您喜欢语法糖,并且可能不太关心性能,那么我想到的这个想法如何:

构建自己的类,封装map并返回自己的迭代器,迭代器在每次迭代器加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";

原谅我这个班的名字,我今天觉得"滑稽"。

还有一个个人建议:不要使用这个类。没有人会知道你的代码里发生了什么。编写规则的、简单的代码。省一行字是不值得的。你之后的下一个程序员会很感激(从经验来看,已经调试了很多代码,人们做了这样"聪明"的事情来节省几行)。

可以这样修改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";
}