7

I want to write a function that fail-safe accesses std::map.

At many places in my code I want to access a std::map by key, but in case the key does not exist, I want to have a kind of default value instead of an exception (which is a lot of code for "nothing").

I wrote this template based function

template <typename T1, typename T2>
T2 mapGetByKey(std::map<T1, T2>& map, T1 key, T2 defaultValue={})
{
    auto it = map.find(key);

    if (it != map.end())
    {
        return it->second;
    }

    return defaultValue;
};

It works great. But for a std::map<int, const char*> I would like to have a different behaviour. So I could add this specialization:

template <typename T1>
const char* mapGetByKey(std::map<T1, const char*>& map, T1 key, const char* defaultValue="")
{
    auto it = map.find(key);

    if (it != map.end())
    {
        return it->second;
    }

    return defaultValue;
};

It works, too. But I think it's a lof of code for just one case.

Does anybody have an idea how to save lines without settings the defaultValue to "" for calls on std::map<int, const char*>?

Is there a way to have differentiate between types at compile time, maybe with some ifdef or something like that?

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
Schubi Duah
  • 309
  • 1
  • 7
  • Put the implementation in a third function (without defaults) and call that from the existing templates –  Aug 14 '15 at 10:24
  • did you think about setting an abstract map value struct that wrap your result ? you can then make the difference between the default and result value . – Anis Belaid Aug 14 '15 at 10:40

3 Answers3

4

Option #1

template <typename T>
T defaultValue()
{
    return {};
}

template <>
const char* defaultValue<const char*>()
{
    return "default string";
}

template <typename T1, typename T2>
T2 mapGetByKey(std::map<T1, T2>& map, const T1& key)
{
    auto it = map.find(key);

    if (it != map.end())
    {
        return it->second;
    }

    return defaultValue<T2>();
}

DEMO 1

Option #2

template <typename T> struct identity { using type = T; };

template <typename T1, typename T2>
T2 mapGetByKey(std::map<T1, T2>& map, T1 key, const typename identity<T2>::type& defaultValue = {})
{
    auto it = map.find(key);

    if (it != map.end())
    {
        return it->second;
    }

    return defaultValue;
}

template <typename T1>
const char* mapGetByKey(std::map<T1, const char*>& map, const T1& key)
{
    return mapGetByKey(map, key, "default string");
}

DEMO 2

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Version 2 sounds and seems good, but when trying to add a 3rd parameter for defaultValue it will run into an infinite loop (because it calls itself instead of the general version). – Schubi Duah Aug 14 '15 at 10:36
  • @SchubiDuah if you add a default value, overload resolution picks the first version, and you don't even want the second one to be called – Piotr Skotnicki Aug 14 '15 at 10:46
  • 1
    be aware that map doesn't always have two template parameters, it could have more, and when it does, both of your examples fail – relaxxx Aug 14 '15 at 13:23
1

Thank you so much! I did not see this simple but very effective possibilites.

Combining the two methods while adding the functionality of situation based different defaultValue I did finnally do this:

template <typename T>
constexpr T mapGetByKeyDefaultValue()
{
    return {};
};

template <>
constexpr const char* mapGetByKeyDefaultValue<const char*>()
{
    return "";
};

template <typename T1, typename T2>
T2 mapGetByKey(std::map<T1, T2>& map, T1 key, T2 defaultValue=mapGetByKeyDefaultValue<T2>())
{
    auto it = map.find(key);

    if (it != map.end())
    {
        return it->second;
    };

    return defaultValue;
};
Schubi Duah
  • 309
  • 1
  • 7
0

You can add a template function for the default value, and add its specialized version for the specific type:

template <typename T>
T default_value() { return {}; }
template <>
const char* default_value<const char*>() { return ""; }

And then

template <typename T1, typename T2>
T2 mapGetByKey(std::map<T1, T2>& map, T1 key, T2 defaultValue=default_value<T2>())

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405