Home > Net >  Generic way to select a map based on data type of key
Generic way to select a map based on data type of key

Time:07-27

I have a few disjoint maps which map unique types to strings. For example:

enum class Colors {RED, GREEN};
enum class Days {MON, SUN};

std::map <Colors, std::string> map1{
    {Colors::RED, "red"},
    {Colors::GREEN, "green"},
};

std::map <Days, std::string> map2{
    {Days::MON, "mon"},
    {Days::SUN, "sun"},
};

Given a key, I want to be able to extract its value transparently from the corresponding map (there is exactly one map for a type).

I considered a wrapper function like so

template <typename T>
auto extractValue(T k){
// Somehow use type T to switch maps and return value corresponding to k
}

I'm however not sure how/if this can be achieved. If this is not possible, what could be an alternate approach where I can exploit the fact that there is exactly one map per type.

I'm using C 20.

Edit: I can achieve this by writing overloads of extractValue for each type, but I'm looking for a more "generic" approach.

CodePudding user response:

In C 17 and later, you can use if constexpr, eg:

template <typename T>
auto extractValue(T k){
    if constexpr (std::is_same_v<T, Colors>) {
        return map1[k];
    }
    else if constexpr (std::is_same_v<T, Days>) {
        return map2[k];
    }
    return std::string{};
}

Otherwise, you can use template specialization, eg:

template <typename T>
auto extractValue(T k){
    return std::string{};
}

template <>
auto extractValue<Colors>(Colors k){
    return map1[k];
}

template <>
auto extractValue<Days>(Days k){
    return map2[k];
} 

Or, you could just simply overload the function, eg:

auto extractValue(Colors k){
    return map1[k];
}

auto extractValue(Days k){
    return map2[k];
}

CodePudding user response:

You can overload function extractValue() for each enum type:

auto extractValue(Colors color) {
    return map1[color];
}

auto extractValue(Days day) {
    return map2[day];
}

int main() {
    std::cout << extractValue(Days::MON) << '\n';
    std::cout << extractValue(Colors::RED) << '\n';
    return 0;
}

CodePudding user response:

Given the parameters of your question, I could recommend two options. The first one with C 17's if constexpr makes everything quite optimal:

template <typename T>
auto extractValue(T k) {
    if constexpr (std::is_same_v<T, Colors>) {
        return map1[k];
    }
    else if constexpr (std::is_same_v<T, Days>) {
        return map2[k];
    }

    throw new ArgumentException("Unrecognized type");
}

This is however not that extendible (if at some point you add a new type), even though the exception is thrown and you know you have to add a new if clause, it isn't always apparent in production code.

I would recommend just sticking to function overloads and ditch the template.

string extractValue(std::map<Colors, string> map, Colors key) {
   return map[key];
}

and so on... You can still call the overloads from a precursor function template in which you don't know the exact types.

Or better yet, just template the key type:

string extractValue<T>(std::map<T, string> map, T key) {
   return map[key];
}

This all depends on the complexity of your functions though. So pick your best choice.

  • Related