如何使用 C++17 的结构化绑定检测 std::map 的最后一次迭代?
How to detect the last iteration of std::map using structured bindings from C++17?
如何使用结构化绑定检测地图的最后一次迭代?
这是一个具体的例子:我有以下简单代码,我使用 C++17 中的结构化绑定从 std::map
中打印元素:
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
// using structured bindings, C++17
for (auto const& [key, value] : mymap) {
std::cout << "key: " << key << ", value: " << value << ", ";
}
return 0;
}
问题是,这将导致尾随逗号,即
key: a, value: 0, key: b, value: 1, key: c, value: 2, key: d, value: 3,
受这个问题的启发:How can I detect the last iteration in a loop over std::map?,我可以用迭代器编写代码来打印出 std::map
内容而不带尾随逗号,即
for (auto iter = mymap.begin(); iter != mymap.end(); ++iter){
// detect final element
auto last_iteration = (--mymap.end());
if (iter==last_iteration) {
std::cout << "\"" << iter->first << "\": " << iter->second;
} else {
std::cout << "\"" << iter->first << "\": " << iter->second << ", ";
}
}
如何用 for (auto const& [key, value] : mymap)
做到这一点?如果我知道 std::map
中的最后一个键,我可以为它写一个条件;但是有没有不求助于 iter
的另一种方法?
简短的回答,你不能。 (这不是范围循环的用途。)
你可以做的是将原始范围分成两个子范围:
for(auto const& [key, value] : std::ranges::subrange(mymap.begin(), std::prev(mymap.end())) ) {
std::cout << "key: " << key << ", value: " << value << ", ";
}
for(auto const& [key, value] : std::ranges::subrange(std::prev(mymap.end()), mymap.end()) ) {
std::cout << "key: " << key << ", value: " << value;
}
https://godbolt.org/z/4EWYjMrqG
或更实用一点:
for(auto const& [key, value] : std::ranges::views::take(mymap, mymap.size() - 1)) {
std::cout << "key: " << key << ", value: " << value << ", ";
}
for(auto const& [key, value] : std::ranges::views::take(std::ranges::views::reverse(mymap), 1)) {
std::cout << "key: " << key << ", value: " << value;
}
或者您可以“索引”范围内的元素。 (STD 范围仍然缺少 zip
来做到这一点。)
#include <iostream>
#include <map>
#include <string>
#include <boost/range/adaptor/indexed.hpp>
int main() {
std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
for(auto const& [index, kvp] : mymap | boost::adaptors::indexed(0) ) {
auto const& [key, value] = kvp;
if(index != mymap.size() - 1) {std::cout << "key: " << key << ", value: " << value << ", ";}
else {std::cout << "key: " << key << ", value: " << value;}
}
return 0;
}
https://godbolt.org/z/f74Pj1Gsz
(令人惊讶的是,Boost.Ranges 与结构化绑定一起工作,就好像它是全新的一样。)
如您所见,重点是您通过强迫自己使用 range-for 循环来对抗该语言。
使用基于迭代器的循环看起来更有吸引力。
当我需要位置和数据时,我编写了一个 iterators_of
适配器,它接受一个范围,returns 它的迭代器范围。
for( auto it:iterators_of(foo) ){
auto const&[key,value]=*it;
// blah
if (std:next(it)!=foo.end())
std::cout<<',';
}
的迭代器很短。
template<class T>
struct index{
T t;
T operator*()const{return t;}
index& operator++(){++t; return *this;}
index operator++(int)&{auto self=*this; ++*this; return self;}
bool operator==(index const&)=default;
auto operator<=>(index const&)=default;
};
然后将其扩充为完整的迭代器或直接编写
template<class It, class Sent=It>
struct range{
It b;
Sent e;
It begin()const{return b;}
Sent end()const{return e;}
};
template<class R>
auto iterators_of(R&& r){
using std::begin; using std::end;
return range{index{begin(r)},index{end(r)}};
}
代码不多。
我发现这比使用迭代器迭代手动 futzing 更好。
从 C++20 开始,您可以使用 init 语句并将其编写为:
#include <iostream>
#include <map>
#include <string>
int main()
{
std::map<std::size_t, std::string> map{ {11, "a"}, {13, "b"}, {22, "c"}, {32, "d"} };
std::size_t end = map.size() - 1;
for (std::size_t n{ 0 }; auto const& [key, value] : map)
{
std::cout << "key: " << key << ", value: " << value;
if ((n++) != end) std::cout << ", ";
}
return 0;
}
在已经看到三种可能的解决方案之后,这里是第四种(而且相当简单)......
因此,我将分隔符移到了输出的开头,并注意在第二次迭代之前修改它:
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
// using structured bindings, C++17
const char* sep = "";
for ( auto const& [key, value] : mymap) {
std::cout << sep << "key: " << key << ", value: " << value;
sep = ", ";
}
return 0;
}
输出:
key: a, value: 0, key: b, value: 1, key: c, value: 2, key: d, value: 3
Yakk 的回答启发我使用 Ranges 编写 iterators_of
而没有引入新的 class:
#include <iostream>
#include <map>
#include <string>
#include <ranges>
template<class Range>
auto iterators_of(Range&& r){
return std::ranges::views::iota(std::begin(r), std::end(r));
}
int main() {
std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
for(auto it : iterators_of(mymap) ) {
auto const& [key, value] = *it;
if(std::next(it) != mymap.end()) {std::cout << "key: " << key << ", value: " << value << ", ";}
else {std::cout << "key: " << key << ", value: " << value;}
}
return 0;
}
对于除第一个键和值对之外的所有键和值对,在开头打印 ,
怎么样?
IF first_key_value_pair:
print(key, value)
ELSE:
print(',' + (key, value))
我们可以使用布尔值来处理第一个键值对。
std::map<char, int> m = {{'a', 1}, {'b', 1}, {'c', 1}, {'d', '1'}};
bool first = true;
for(const auto& [key, value] : m){
if(first) first = false; else std::cout << ", ";
std::cout << "Key: " << key << ", Value: " << value;
}
输出:Key: a, Value: 1, Key: b, Value: 1, Key: c, Value: 1, Key: d, Value: 49
如何使用结构化绑定检测地图的最后一次迭代?
这是一个具体的例子:我有以下简单代码,我使用 C++17 中的结构化绑定从 std::map
中打印元素:
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
// using structured bindings, C++17
for (auto const& [key, value] : mymap) {
std::cout << "key: " << key << ", value: " << value << ", ";
}
return 0;
}
问题是,这将导致尾随逗号,即
key: a, value: 0, key: b, value: 1, key: c, value: 2, key: d, value: 3,
受这个问题的启发:How can I detect the last iteration in a loop over std::map?,我可以用迭代器编写代码来打印出 std::map
内容而不带尾随逗号,即
for (auto iter = mymap.begin(); iter != mymap.end(); ++iter){
// detect final element
auto last_iteration = (--mymap.end());
if (iter==last_iteration) {
std::cout << "\"" << iter->first << "\": " << iter->second;
} else {
std::cout << "\"" << iter->first << "\": " << iter->second << ", ";
}
}
如何用 for (auto const& [key, value] : mymap)
做到这一点?如果我知道 std::map
中的最后一个键,我可以为它写一个条件;但是有没有不求助于 iter
的另一种方法?
简短的回答,你不能。 (这不是范围循环的用途。) 你可以做的是将原始范围分成两个子范围:
for(auto const& [key, value] : std::ranges::subrange(mymap.begin(), std::prev(mymap.end())) ) {
std::cout << "key: " << key << ", value: " << value << ", ";
}
for(auto const& [key, value] : std::ranges::subrange(std::prev(mymap.end()), mymap.end()) ) {
std::cout << "key: " << key << ", value: " << value;
}
https://godbolt.org/z/4EWYjMrqG
或更实用一点:
for(auto const& [key, value] : std::ranges::views::take(mymap, mymap.size() - 1)) {
std::cout << "key: " << key << ", value: " << value << ", ";
}
for(auto const& [key, value] : std::ranges::views::take(std::ranges::views::reverse(mymap), 1)) {
std::cout << "key: " << key << ", value: " << value;
}
或者您可以“索引”范围内的元素。 (STD 范围仍然缺少 zip
来做到这一点。)
#include <iostream>
#include <map>
#include <string>
#include <boost/range/adaptor/indexed.hpp>
int main() {
std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
for(auto const& [index, kvp] : mymap | boost::adaptors::indexed(0) ) {
auto const& [key, value] = kvp;
if(index != mymap.size() - 1) {std::cout << "key: " << key << ", value: " << value << ", ";}
else {std::cout << "key: " << key << ", value: " << value;}
}
return 0;
}
https://godbolt.org/z/f74Pj1Gsz
(令人惊讶的是,Boost.Ranges 与结构化绑定一起工作,就好像它是全新的一样。)
如您所见,重点是您通过强迫自己使用 range-for 循环来对抗该语言。 使用基于迭代器的循环看起来更有吸引力。
当我需要位置和数据时,我编写了一个 iterators_of
适配器,它接受一个范围,returns 它的迭代器范围。
for( auto it:iterators_of(foo) ){
auto const&[key,value]=*it;
// blah
if (std:next(it)!=foo.end())
std::cout<<',';
}
的迭代器很短。
template<class T>
struct index{
T t;
T operator*()const{return t;}
index& operator++(){++t; return *this;}
index operator++(int)&{auto self=*this; ++*this; return self;}
bool operator==(index const&)=default;
auto operator<=>(index const&)=default;
};
然后将其扩充为完整的迭代器或直接编写
template<class It, class Sent=It>
struct range{
It b;
Sent e;
It begin()const{return b;}
Sent end()const{return e;}
};
template<class R>
auto iterators_of(R&& r){
using std::begin; using std::end;
return range{index{begin(r)},index{end(r)}};
}
代码不多。
我发现这比使用迭代器迭代手动 futzing 更好。
从 C++20 开始,您可以使用 init 语句并将其编写为:
#include <iostream>
#include <map>
#include <string>
int main()
{
std::map<std::size_t, std::string> map{ {11, "a"}, {13, "b"}, {22, "c"}, {32, "d"} };
std::size_t end = map.size() - 1;
for (std::size_t n{ 0 }; auto const& [key, value] : map)
{
std::cout << "key: " << key << ", value: " << value;
if ((n++) != end) std::cout << ", ";
}
return 0;
}
在已经看到三种可能的解决方案之后,这里是第四种(而且相当简单)......
因此,我将分隔符移到了输出的开头,并注意在第二次迭代之前修改它:
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
// using structured bindings, C++17
const char* sep = "";
for ( auto const& [key, value] : mymap) {
std::cout << sep << "key: " << key << ", value: " << value;
sep = ", ";
}
return 0;
}
输出:
key: a, value: 0, key: b, value: 1, key: c, value: 2, key: d, value: 3
Yakk 的回答启发我使用 Ranges 编写 iterators_of
而没有引入新的 class:
#include <iostream>
#include <map>
#include <string>
#include <ranges>
template<class Range>
auto iterators_of(Range&& r){
return std::ranges::views::iota(std::begin(r), std::end(r));
}
int main() {
std::map<std::string, size_t> mymap {{"a", 0}, {"b", 1}, {"c", 2}, {"d", 3}};
for(auto it : iterators_of(mymap) ) {
auto const& [key, value] = *it;
if(std::next(it) != mymap.end()) {std::cout << "key: " << key << ", value: " << value << ", ";}
else {std::cout << "key: " << key << ", value: " << value;}
}
return 0;
}
对于除第一个键和值对之外的所有键和值对,在开头打印 ,
怎么样?
IF first_key_value_pair:
print(key, value)
ELSE:
print(',' + (key, value))
我们可以使用布尔值来处理第一个键值对。
std::map<char, int> m = {{'a', 1}, {'b', 1}, {'c', 1}, {'d', '1'}};
bool first = true;
for(const auto& [key, value] : m){
if(first) first = false; else std::cout << ", ";
std::cout << "Key: " << key << ", Value: " << value;
}
输出:Key: a, Value: 1, Key: b, Value: 1, Key: c, Value: 1, Key: d, Value: 49