Reputation: 54859
I want to set the keys of a std::map
using the elements of a std::set
(or a std::vector
).
Something like the following...
std::set<int> keys = { 3,4,6 };
std::map<int,string> results(keys); // syntax error
Can this be done without explicitly iterating over the set?
Upvotes: 2
Views: 2098
Reputation: 69854
Can this be done without explicitly iterating over the set?
No. There is no way to know the keys in the set without iterating over it. You may write functions to make it appear as if there is an implicit transformation, but those functions must ultimately iterate the source collection.
A simple way is as follows:
#include <set>
#include <map>
#include <string>
auto build_map(const std::set<int>& source) -> std::map<int,std::string>
{
std::map<int,std::string> results;
for (auto const& i : source) {
results[i];
}
return results;
}
int main()
{
std::set<int> keys = { 3,4,6 };
auto results = build_map(keys);
}
Of course we may templatise if that improves readability:
#include <set>
#include <vector>
#include <unordered_set>
#include <map>
#include <string>
#include <utility>
template<class MappedType, class SourceContainer>
auto build_map(SourceContainer&& source)
{
using source_type = std::decay_t<SourceContainer>;
using key_type = typename source_type::value_type;
std::map<key_type , MappedType> results;
for (auto const& i : source) {
results[i];
}
return results;
}
int main()
{
std::set<int> keys = { 3,4,6 };
auto results = build_map<std::string>(keys);
// also
results = build_map<std::string>(std::vector<int>{3, 4, 6});
results = build_map<std::string>(std::unordered_set<int>{3, 4, 6});
}
Upvotes: 1
Reputation: 302643
You can't. A map
is not a set
. They're fundamentally different containers, even if the underlying structure is similar.
That said, anything's possible. The range constructor of std::map
is linear time if the elements of the range are already sorted, which set
guarantees for us. So all you need to do is apply a transformer to every element to produce a new range. The simplest would be to just use something like boost::make_transform_iterator
(or roll your own):
template <class K, class F
class V = decltype(std::declval<F&>()(std::declval<K const&>()))::second_type>
std::map<K, V> as_map(std::set<K> const& s, F&& f) {
return std::map<K,V>(
boost::make_transform_iterator(s.begin(), f),
boost::make_transform_iterator(s.end(), f));
}
std::map<int,string> results =
as_map(keys, [](int i){
return std::make_pair(i, std::string{});
});
which if you always will want default initialization, can just reduce to:
template <class V, class K>
std::map<K, V> as_map_default(std::set<K> const& s) {
auto f = [](K const& k) { return std::make_pair(k, V{}); }
return std::map<K,V>(
boost::make_transform_iterator(s.begin(), f),
boost::make_transform_iterator(s.end(), f));
}
std::map<int,string> results = as_map_default<string>(keys);
Upvotes: 2