Reputation: 1858
How could I detect the last iteration of a map using structured bindings?
Here's a concrete example: I have the following simple code whereby I print elements from an std::map
using structured bindings from C++17:
#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;
}
The problem is, this will result in a trailing comma, i.e.
key: a, value: 0, key: b, value: 1, key: c, value: 2, key: d, value: 3,
Inspired by this question: How can I detect the last iteration in a loop over std::map?, I can write code with an iterator to print out the std::map
contents without the trailing comma, i.e.
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 << ", ";
}
}
How does one do this with for (auto const& [key, value] : mymap)
? If I know the final key in std::map
, I could write an conditional for it; but is there another way without resorting to iter
?
Upvotes: 1
Views: 619
Reputation: 16242
Yakk's answer inspired me to write an iterators_of
using Ranges and without introducing a new 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;
}
https://godbolt.org/z/rx15eMnWh
Upvotes: 2
Reputation: 575
What about printing ,
in the beginning for all key and value pairs except the first one.
IF first_key_value_pair:
print(key, value)
ELSE:
print(',' + (key, value))
We can use a boolean to take care of the first key-value pair.
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;
}
Output: Key: a, Value: 1, Key: b, Value: 1, Key: c, Value: 1, Key: d, Value: 49
Upvotes: 2
Reputation: 16242
Short answer, you can't. (That is not what range loops are for.) What you can do is divide the original range in two subranges:
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
or a bit more functional:
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;
}
or you can "index" the elements of the range. (STD Ranges still lacks zip
to do this.)
#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
(Amazing that Boost.Ranges works with structured bindings as if it were brand new.)
The whole point is, as you see, you are fighting against the language by forcing yourself to use a range-for loop. It looks more attractive to use the iterator based loop.
Upvotes: 3
Reputation: 20141
After having already seen three possible solutions, here a fourth (and admittedly rather simple)…
Thereby, I moved the separator to the begin of the output and take care that it's modified before the second iteration:
#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;
}
Output:
key: a, value: 0, key: b, value: 1, key: c, value: 2, key: d, value: 3
Upvotes: 2
Reputation: 12891
From C++20 onward you can use an init-statement and write it like this:
#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;
}
Upvotes: 1
Reputation: 275405
When I need location as well as data, I write an iterators_of
adapter that takes a range, and returns a range of its iterators.
for( auto it:iterators_of(foo) ){
auto const&[key,value]=*it;
// blah
if (std:next(it)!=foo.end())
std::cout<<',';
}
iterators of is short.
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;
};
then augment that to be a full iterator or just write
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)}};
}
which isn't much code.
I find this is better than manually futzing with iterator iteration.
Upvotes: 2