Search code examples
c++templatestype-traitsdecltypeenable-if

C++ template type_trait enable_if a class is a map


#include <iostream>
#include <any>
#include <vector>
#include <map>
#include <unordered_map>
#include <string>
using namespace std;

// enable_if_t = MapType implements .find(KeyType key), .begin(), .end(), and .begin() 
// and MapType::iterator it has it->first and it->second
template<typename MapType>
decltype(declval<MapType>().begin()->second) MapGetOrDefault(
    MapType mymap, 
    decltype(declval<MapType>().begin()->first) key, 
    decltype(declval<MapType>().begin()->second) defaultValue = decltype(declval<MapType>().begin()->second){}
    )
{
    auto it = mymap.find(key);
    if (it!=mymap.end()) return it->second;
    else return defaultValue;
}

int main()
{   
    map<string, int> a;
    unordered_map<int, string> b;
    
    a["1"] = 2;
    cout << MapGetOrDefault(a, "1") << " " << MapGetOrDefault(a, "2") << " " << MapGetOrDefault(a, "2", -1) << "\n";

    b[1] = "hello";
    cout << MapGetOrDefault(b, 1) << " " << MapGetOrDefault(b, 2) << " " << MapGetOrDefault(b, 3, "world") << "\n";

    return 0;
}

I'm trying to make a generic get_or_default() function that can be used with all types of map. There are 4 conditions that a class must meet to be counted as map, like shown in the code comment.

How can I do this using C++ type traits? Also, how to change decltype(declval<MapType>().begin()->first) into something more clean?

Edit: is enable_if_t even needed in this case? My goal is to just prevent compilation


Solution

  • Maps have member aliases mapped_type and key_type (and value_type) that you can use :

    #include <iostream>
    #include <any>
    #include <vector>
    #include <map>
    #include <unordered_map>
    #include <string>
    using namespace std;
    
    template<typename MapType>
    typename MapType::mapped_type MapGetOrDefault(
        MapType mymap, 
        typename MapType::key_type key, 
        typename MapType::mapped_type defaultValue = typename MapType::mapped_type{}
        )
    {
        auto it = mymap.find(key);
        if (it!=mymap.end()) return it->second;
        else return defaultValue;
    }
    
    int main()
    {   
        map<string, int> a;
        unordered_map<int, string> b;
        
        a["1"] = 2;
        cout << MapGetOrDefault(a, "1") << " " << MapGetOrDefault(a, "2") << " " << MapGetOrDefault(a, "2", -1) << "\n";
    
        b[1] = "hello";
        cout << MapGetOrDefault(b, 1) << " " << MapGetOrDefault(b, 2) << " " << MapGetOrDefault(b, 3, "world") << "\n";
    
        return 0;
    }
    

    Template aliases can help for more terse syntax:

    template <typename T> using mapped_type = typename T::mapped_type;
    
    template <typename T> using key_type = typename T::key_type;
    
    template<typename MapType>
    mapped_type<MapType> MapGetOrDefault(
        MapType mymap, 
        key_type<MapType> key, 
        mapped_type<MapType> defaultValue = mapped_type<MapType>{}
        )
    {
        auto it = mymap.find(key);
        if (it!=mymap.end()) return it->second;
        else return defaultValue;
    }
    

    PS: I didnt change it because it wasn't part of the question, but you should not make a copy of the map when passing it to the function, rather use const MapType& mymap for the argument.

    PPS: As mentioned by Caleth in a comment, with a transparent comparator, ie one that has a Comparator::is_transparent member type, you might want to support the overload (4) listed here: https://en.cppreference.com/w/cpp/container/map/find by introducing another template argument KeyType that can be deduced from the key paramter.