在C++中是否可以使用命名变量(例如,键和值)而不是.first和.second来执行std :: map <>"for element:container"迭代?

Jam*_*rgy 26 c++ iteration dictionary readability c++11

我不知道该搜索什么.我发现重命名地图迭代器的第一个和第二个,但它不是我想要做的.

这是我想要做的[见下面的无意义的C++代码].有可能接近这个吗?否则将只需要选择"调整"迭代器作为循环内部的第一行我想.

// what I want to do:
std::map<int, std::string> my_map;
// ... populate my_map
for(auto key, auto & value: my_map){
    // do something with integer key and string value
}
Run Code Online (Sandbox Code Playgroud)

C++ 11很好,但如果可能,请避免使用.

我得到的最接近的是

// TODO, can this be templated?
struct KeyVal{
    int & id;
    std::string & info;

    template <typename P>
    KeyVal(P & p)
        : id(p.first)
        , info(p.second)
    {
    }
};

//...
for ( KeyVal kv : my_map ){
    std::cout << kv.info;
}
Run Code Online (Sandbox Code Playgroud)

但这意味着为每个地图编写一个适配器类:(

// slightly joke answer/"what could possibly go wrong?"
#define key first
#define value second
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 17

以下Barry启发的方法是编写一个范围适配器.

没有boost或类似的库支持这样做是一件痛苦的事,但是:

  1. 写一个范围模板.它存储2 class iterator秒和具有begin()end()方法(和其他任何你想要的).

  2. 编写一个转换迭代器适配器.它需要一个迭代器,并将其包装起来,以便它的值类型由一些函数对象F转换.

  3. 写一个to_kv变换器,取一个std::pair<K, V> cv&并返回一个struct kv_t { K cv& key; V cv& value; }.

  4. 将3连接成2进1并调用它as_kv.它需要一系列对,并返回一系列键值.

您最终得到的语法是:

std::map<int, std::string> m;

for (auto kv : as_kv(m)) {
  std::cout << kv.key << "->" << kv.value << "\n";
}
Run Code Online (Sandbox Code Playgroud)

这很好.

这是一个极简主义的解决方案,实际上并不创建合法的迭代器,但支持for(:):

template<class Key, class Value>
struct kv_t {
  Key&& key;
  Value&& value;
};

// not a true iterator, but good enough for for(:)
template<class Key, class Value, class It>
struct kv_adapter {
  It it;
  void operator++(){ ++it; }
  kv_t<Key const, Value> operator*() {
    return {it->first, it->second};
  }
  friend bool operator!=(kv_adapter const& lhs, kv_adapter const& rhs) {
    return lhs.it != rhs.it;
  }
};
template<class It, class Container>
struct range_trick_t {
  Container container;
  range_trick_t(Container&&c):
    container(std::forward<Container>(c))
  {}
  It begin() { return {container.begin()}; }
  It end() { return {container.end()}; }
};
template<class Map>
auto as_kv( Map&& m ) {
  using std::begin;
  using iterator = decltype(begin(m)); // no extra (())s
  using key_type = decltype((begin(m)->first)); // extra (())s on purpose
  using mapped_type = decltype((begin(m)->second)); // extra (())s on purpose
  using R=range_trick_t<
    kv_adapter<key_type, mapped_type, iterator>,
    Map
  >;
  return R{std::forward<Map>(m)};
}
std::map<int, std::string> m() { return {{0, "Hello"}, {2, "World"}}; }
Run Code Online (Sandbox Code Playgroud)

这是非常小的,但有效.我通常不会鼓励这种半for(:)循环伪迭代器用于循环; 使用真实的迭代器只是一个适度的额外成本,并不会让人们后来感到惊讶.

实例

(现在有临时地图支持.不支持平面C数组......)

范围技巧存储容器(可能是引用),以便将临时容器复制到for(:)循环持续时间内存储的对象中.非临时容器Container类型是Foo&某种类型,因此它不会产生冗余副本.

另一方面,kv_t显然只存储引用.可能有一个奇怪的迭代器返回临时数据来破坏这个kv_t实现,但我不确定如何避免它一般而不牺牲在更常见的情况下的性能.


如果您不喜欢上述kv.部分,我们可以做一些解决方案,但它们并不是那么干净.

template<class Map>
struct for_map_t {
  Map&& loop;
  template<class F>
  void operator->*(F&& f)&&{
    for (auto&& x:loop) {
      f( decltype(x)(x).first, decltype(x)(x).second );
    }
  }
};
template<class Map>
for_map_t<Map> map_for( Map&& map ) { return {std::forward<Map>(map)}; }
Run Code Online (Sandbox Code Playgroud)

然后:

map_for(m)->*[&](auto key, auto& value) {
  std::cout << key << (value += " ") << '\n';
};
Run Code Online (Sandbox Code Playgroud)

足够近?

实例

关于一流元组(以及因此对)的一些提议可能会给你类似的东西,但我不知道提案的状态.

如果进入C++,你可能会得到的语法如下所示:

for( auto&& [key, value] : container )
Run Code Online (Sandbox Code Playgroud)

关于上述->*可憎的评论:

因此,->*它被用作operator bind来自Haskell(与隐式元组解包)一起使用,我们正在为它提供一个lambda,它获取地图中包含的数据并返回void.(Haskell-esque)返回类型变成了void(无)的映射,我将其视为void.

这项技术有一个问题:你输了break;,continue;哪个很糟糕.

一个不那么hackey Haskell启发的变体会期望lambda返回类似的东西void | std::experimental::expected<break_t|continue_t, T>,如果T没有void返回任何内容,如果T是元组类型返回一个地图,并且如果T是一个地图连接返回的地图类型.根据lambda想要的内容(SFINAE样式检测),它也可以解包或不解包所包含的元组.

但这对于答案来说有点多了; 这个题外话指出,上述编程风格并不是一个完整的死胡同.然而,它在C++中是非常规的.

  • 就提案而言,还有结构化绑定 - 这可能是最相关的(`for(auto && [key,value]:map){...}`) (2认同)
  • @MaximEgorushkin如果`x`的类型是'auto &&`,那么`decltype(x)(x)`没有副本:`x`保证是(某些)引用类型,并且转换为不同的引用类型不会复制.如果`x`的类型是`auto`,它确实是,并且`std :: forward <decltype(x)>(x)`是合适的.通常情况下,我使用`auto &&`变量,而不是`auto`变量来完善.`decltype(x)(x)`的简洁性是我更喜欢`std :: forward <decltype(x)>(x)`的原因,特别是在lambdas中.正向变化将与`auto &&`一起使用,因为如果你传递一个`int &&`或类似的类型以及`int`,`forward`就可以工作. (2认同)

Bar*_*rry 6

你可以写一个类模板:

template <class K, class T>
struct MapElem {
    K const& key;
    T& value;

    MapElem(std::pair<K const, T>& pair)
        : key(pair.first)
        , value(pair.second)
    { }
};
Run Code Online (Sandbox Code Playgroud)

具有能够写出优势keyvalue但不必指定类型的缺点:

for ( MapElem<int, std::string> kv : my_map ){
    std::cout << kv.key << " --> " << kv.value;
}
Run Code Online (Sandbox Code Playgroud)

如果my_map是这样的话,这将无法奏效const.你必须做的事情如下:

template <class K, class T>
struct MapElem {
    K const& key;
    T& value;

    MapElem(std::pair<K const, T>& pair)
        : key(pair.first)
        , value(pair.second)
    { }

    MapElem(const std::pair<K const, std::remove_const_t<T>>& pair)
        : key(pair.first)
        , value(pair.second)
    { }
};

for ( MapElem<int, const std::string> kv : my_map ){
    std::cout << kv.key << " --> " << kv.value;
}
Run Code Online (Sandbox Code Playgroud)

一团糟.现在最好的事情就是习惯于写作.first,.second并希望结构化绑定提案通过,这将允许你真正想要的东西:

for (auto&& [key, value] : my_map) {
    std::cout << key << " --> " << value;
}
Run Code Online (Sandbox Code Playgroud)


小智 5

使用现代 c++17,现在可以使用结构化绑定

#include <map>
#include <string>
#include <iostream>

using namespace std;

int main() {
    map<int, string> my_map;

    my_map[0] = "hello";
    my_map[1] = "world";

    for (auto&& [key, value] : my_map) {
        cout << key << "," << value << "\n";
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

构建它:

$ clang++ -std=c++17 test.cpp -o program
Run Code Online (Sandbox Code Playgroud)

输出:

$ ./program
0,hello
1,world
Run Code Online (Sandbox Code Playgroud)