我不确定要搜索什么。 我找到了Renaming first and second of a map iterator,但这并不是我想做的事情。
这就是我想做的事情[请参阅下面的无意义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
}
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;
}
但这意味着为每个地图编写一个适配器类:(
// slightly joke answer/"what could possibly go wrong?"
#define key first
#define value second
答案 0 :(得分:17)
以下Barry启发的方法是编写一个范围适配器。
在没有boost
或类似的库支持的情况下执行此操作很痛苦,但是:
编写范围模板。它存储了2 class iterator
个,并有begin()
和end()
方法(以及您想要的任何其他方法)。
编写转换迭代器适配器。它需要一个迭代器,并将其包装起来,以便它的值类型由一些函数对象F转换。
撰写一个to_kv
变换器,其中std::pair<K, V> cv&
并返回struct kv_t { K cv& key; V cv& value; }
。
将3连接到2到1并将其称为as_kv
。它需要一系列对,并返回一系列键值。
您最终得到的语法是:
std::map<int, std::string> m;
for (auto kv : as_kv(m)) {
std::cout << kv.key << "->" << kv.value << "\n";
}
这很好。
这是一个极简主义的解决方案,它实际上并没有创建合法的迭代器,但支持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"}}; }
非常最小,但有效。我通常不会鼓励这种用于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)}; }
然后:
map_for(m)->*[&](auto key, auto& value) {
std::cout << key << (value += " ") << '\n';
};
足够接近?
有一些关于一流元组(以及因此对)的建议可能会给你这样的东西,但我不知道提案的状态。
如果进入C ++,你可能会得到的语法如下所示:
for( auto&& [key, value] : container )
对上述->*
憎恶的评论:
因此,->*
被用作来自Haskell的operator bind
(与隐式元组解包一起),我们正在为它提供一个lambda,它获取地图中包含的数据并返回void 。 (Haskell-esque)返回类型变成了void(无)的映射,我将其视为无效。
该技术存在一个问题:您丢失了break;
和continue;
。
一个不那么hackey Haskell启发的变体会期望lambda返回类似void | std::experimental::expected<break_t|continue_t, T>
的内容,如果T
是void
则返回任何内容,如果T
是元组 - type返回一个map,如果T
是map,则加入返回的map-type。根据lambda想要的内容(SFINAE风格的检测),它也可以解包或不解包所包含的元组。
但对于答案而言,这有点多了;这个题外话指出,上述编程风格并不是一个完整的死胡同。然而,它在C ++中是非常规的。
答案 1 :(得分: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)
{ }
};
具有能够编写key
和value
的优点,但缺点是必须指定类型:
for ( MapElem<int, std::string> kv : my_map ){
std::cout << kv.key << " --> " << kv.value;
}
如果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;
}
这是一团糟。现在最好的事情就是习惯于写.first
和.second
,并希望结构化绑定提案通过,这将允许你真正想要的东西:
for (auto&& [key, value] : my_map) {
std::cout << key << " --> " << value;
}
答案 2 :(得分:3)
最接近使用的std::tie
:
std::map<int, std::string> my_map;
int key;
std::string value;
for(auto&& p: my_map)
{
std::tie(key, value) = p;
std::cout << key << ": " << value << std::endl;
}
当然,表达式不能放在for range循环中,所以可以使用宏来表示表达式:
#define FOREACH(var, cont) \
for(auto && _p:cont) \
if(bool _done = false) {} \
else for(var = std::forward<decltype(_p)>(_p); !_done; _done = true)
那么std::tie
可以直接在循环中使用:
std::map<int, std::string> my_map;
int key;
std::string value;
FOREACH(std::tie(key, value), my_map)
{
std::cout << key << ": " << value << std::endl;
}
答案 3 :(得分:3)
使用现代c ++ 17,现在可以使用structured bindings。
#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;
}
构建它:
$ clang++ -std=c++17 test.cpp -o program
输出:
$ ./program
0,hello
1,world
答案 4 :(得分:1)
为了提供几乎做你想要的另一种方式,我前一段时间写了这篇文章,以避免我的代码中出现和
.first
:
.second
现在你可以写一些像(假设基于范围的auto pair2params = [](auto&& f)
{
return [f](auto&& p) {
f(p.first, p.second);
};
};
):
for_each
正在运行示例:http://ideone.com/Bs9Ctm
答案 5 :(得分:1)
我通常更喜欢KISS方法:
template<typename KeyValuePair>
typename KeyValuePair::first_type& key(KeyValuePair& kvp)
{
return kvp.first;
}
template<typename KeyValuePair>
const typename KeyValuePair::first_type& key(const KeyValuePair& kvp)
{
return kvp.first;
}
template<typename KeyValuePair>
void key(const KeyValuePair&& kvp) = delete;
template<typename KeyValuePair>
typename KeyValuePair::second_type& value(KeyValuePair& kvp)
{
return kvp.second;
}
template<typename KeyValuePair>
const typename KeyValuePair::second_type& value(const KeyValuePair& kvp)
{
return kvp.second;
}
template<typename KeyValuePair>
void value(const KeyValuePair&& kvp) = delete;
使用如下的用法示例:
for(auto& kvp : my_map) {
std::cout << key(kvp) << " " << value(kvp) << "\n";
}
答案 6 :(得分:1)
在Apache Mesos中,我们使用名为foreachpair
的宏,可以像这样使用:
foreachpair (const Key& key, const Value& value, elems) {
/* ... */
}
您当然可以将Key
和Value
替换为auto
,以及您希望在那里使用的任何限定符。它还支持break
和continue
。
我的最新实现如下:
#define FOREACH_PREFIX BOOST_PP_CAT(foreach_, __LINE__)
#define FOREACH_BODY BOOST_PP_CAT(FOREACH_PREFIX, _body__)
#define FOREACH_BREAK BOOST_PP_CAT(FOREACH_PREFIX, _break__)
#define FOREACH_CONTINUE BOOST_PP_CAT(FOREACH_PREFIX, _continue__)
#define FOREACH_ELEM BOOST_PP_CAT(FOREACH_PREFIX, _elem__)
#define FOREACH_ONCE BOOST_PP_CAT(FOREACH_PREFIX, _once__)
上面的宏通过包含foreachpair
数字为__LINE__
宏中使用的各种组件提供了唯一的名称。
1 #define foreachpair(KEY, VALUE, ELEMS) \
2 for (auto&& FOREACH_ELEM : ELEMS) \
3 if (false) FOREACH_BREAK: break; /* set up the break path */ \
4 else if (bool FOREACH_CONTINUE = false) {} /* var decl */ \
5 else if (true) goto FOREACH_BODY; /* skip the loop exit checks */ \
6 else for (;;) /* determine whether we should break or continue. */ \
7 if (!FOREACH_CONTINUE) goto FOREACH_BREAK; /* break */ \
8 else if (true) break; /* continue */ \
9 else \
10 FOREACH_BODY: \
11 if (bool FOREACH_ONCE = false) {} /* var decl */ \
12 else for (KEY = std::get<0>( \
13 std::forward<decltype(FOREACH_ELEM)>(FOREACH_ELEM)); \
14 !FOREACH_ONCE; FOREACH_ONCE = true) \
15 for (VALUE = std::get<1>( \
16 std::forward<decltype(FOREACH_ELEM)>(FOREACH_ELEM)); \
17 !FOREACH_CONTINUE; FOREACH_CONTINUE = true)
我将逐行完成这一行。
ELEMS
。FOREACH_BREAK
。我们跳转到此循环中的break
标签。FOREACH_CONTINUE
。如果当前迭代正常,或通过true
退出,则continue
为false
,如果当前迭代通过break
退出,则为FOREACH_BODY
。FOREACH_CONTINUE
标签。FOREACH_CONTINUE
标志以确定我们如何退出当前迭代的地方。false
为break
,我们知道我们是通过FOREACH_BREAK
退出的,因此我们跳转到FOREACH_CONTINUE
。true
为break
,我们for (;;)
退出FOREACH_ONCE
循环,将我们带到下一次迭代。for
,它仅用于执行KEY
循环,只声明KEY
一次。FOREACH_ONCE
。VALUE
确保此循环只执行一次。FOREACH_CONTINUE
。break
确保此循环仅执行一次,并指示循环是否已通过std::get
退出。 注意:使用std::tuple
也可以支持来自序列的std::array
或std::vector<std::tuple<int, int>>
。例如,var files = DriveApp.getFiles();
while (files.hasNext()) {
var file = files.next();
}