I’ve been doing a reasonable amount of Clojure development recently and like a lot of other Lisp dialect have marveled at the ease of separately pulling out the keys and values from a map. This is a very common operation after all, but C++ does only appear to support manual key or value extraction from a std::map.
Obviously the code isn’t hard to write. In C++11, the following function will return a vector of all keys in a map:
std::vector<std::string> extract_keys(std::>map<std::string, std::string> const& input_map) {
std::vector<std::string> retval;
for (auto const& element : input_map) {
retval.push_back(element.first);
}
return retval;
}
Same goes for the values:
std::vector<std::string> extract_values(std::map<std::string, std:string>; const& input_map) {
std::vector<std::string> retval;
for (auto const& element : input_map) {
retval.push_back(element.second);
}
return retval;
}
This being C++, we most likely don’t want to go and write the code every time, so we turn it into a set of templates:
template<typename TK, typename TV>
std::vector<TK> extract_keys(std::map<TK, TV> const& input_map) {
std::vector<TK> retval;
for (auto const& element : input_map) {
retval.push_back(element.first);
}
return retval;
}
template<typename TK, typename TV>
std::vector<TV> extract_values(std::map<TK, TV> const& input_map) {
std::vector<TV> retval;
for (auto const& element : input_map) {
retval.push_back(element.second);
}
return retval;
}
The code above is reasonably efficient under most circumstances. Yes, you can improve it by calling retval.reserve()
to make sure we don’t have to reallocate memory for the vector and make some other small tweaks, but overall, the couple of lines above do the job.
They’re also a lot better than finding the above for loop in the middle of a function. That requires me to scratch my head for a couple of seconds before the usual dim light bulb appears over my head and tells me, “hey, someone’s trying to pull the keys out of a map”. Why is this?
Because just having the loop floating around in the middle of your or my code doesn’t document intent, all it documents is that, well, we have a loop that copies stuff. Even if you wrote the code and then come back to it six months later, this is likely to give you a bit of a pause until you remember what it does. Of course you can put a comment right next to it, but that is also a poor attempt at documenting intent. Not to mention that the comment will go out of date faster than the code itself will. Using a function with a descriptive name is much better as you can then read the code and the function name gives you a reasonable expectation what the code is supposed to do.
Of course, if you use Boost, you can just write this piece of code using the boost range libraries:
boost::copy(input_map | boost::adaptors::map_keys,
std::back_inserter(output_vector));
It’s probably not going to do Good Things for your compile time, but it’s succinct, readable and documents intent.