In my code I have a number of places where I need to take an std::vector of things and put it into a std::map indexed by something. For example here are two code snippets:
//sample A
std::map<Mode::Type, std::vector<Mode>> modesByType;
for( const auto& mode : _modes ) {
Mode::Type type = mode.getType();
auto it = modesByType.find( type );
if( it == modesByType.end() ) {
std::vector<Mode> v = { mode };
modesByType.insert( std::pair( type, v ) );
} else {
it->second.push_back( mode );
}
}
//sample B
std::map<unsigned, std::vector<Category>> categoriesByTab;
for( const auto& category : _categories ) {
unsigned tabIndex = category.getTab();
auto it = categoriesByTab.find( tabIndex );
if( it == categoriesByTab.end() ) {
std::vector<Category> v = { category };
categoriesByTab.insert( std::pair( tabIndex, v ) );
} else {
it->second.push_back( category );
}
}
I'd like to generalize this and create a templated function like:
template<typename T, typename V>
std::map<T,std::vector<V>> getMapByType( const std::vector<V>& items, ?? ) {
std::map<T,std::vector<V>> itemsByType;
for( const auto& item : items ) {
unsigned index = ??;
auto it = itemsByType.find( index );
if( it == itemsByType.end() ) {
std::vector<V> v = { item };
itemsByType.insert( std::pair( index, v ) );
} else {
it->second.push_back( item );
}
}
return itemsByType;
}
My question is, how do I define the ?? argument to this function so that I can call the correct V.foo() function to get the index value for the map?
Note, I do not want to make all the classes that this template (V) accepts, inherit from a base class. Can I somehow specify a lambda argument?
CodePudding user response:
You can pass a function that determines the key, something like this:
template <typename V,typename F>
auto getMapByType( const std::vector<V>& items,F func) {
using key_t = std::decay_t<delctype(func(item[0]))>;
std::map<key_t,std::vector<V>> result;
for (const auto& item : items) {
result[ func(item) ].push_back(item);
}
return item;
}
Then you can call it like this
std:vector<Category> vec;
auto m = getMapByType( vec, [](const Category& c) { return c.getTab(); });
or
std:vector<Mode> vec;
auto m = getMapByType( vec, [](const Category& c) { return c.getType(); });
Note that operator[]
does already what you reimplemented. It tries to find an element with the given key. If none is present it inserts a default constructed one, then it returns a reference to the mapped value.
Even without operator[]
you do not need find
and then insert
, because insert
does only insert when no element with given key was present. insert
returns an iterator to the element and a bool
which tells you if the insertion actually took place.
CodePudding user response:
have a pointer to a member fn as an extra parameter
template<typename T, typename V>
std::map<T,std::vector<V>> getMapByType( const std::vector<V>& items, T (V::*fn)()const) {
std::map<T,std::vector<V>> itemsByType;
for( const auto& item : items ) {
T index = (item.*fn)();
auto it = itemsByType.find( index );
if( it == itemsByType.end() ) {
std::vector<V> v = { item };
itemsByType.emplace( index, v );
} else {
it->second.push_back( item );
}
}
return itemsByType;
}
auto res = getMapByType(items, &Category::getTab);