Home > Back-end >  How to template creating a map for custom types
How to template creating a map for custom types

Time:11-13

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);
  • Related