Search code examples
c++dictionarytemplateslambda

How to template creating a map for custom types


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 template 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?


Solution

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