Single line lookup for nested std::map

Viewed 187

Let’s say I have a std::map<int, std::map<int, std::string>>, is there a way to lookup directly for a string if you are given the two keys in a shorter statement?

Some syntax sugar for:

std::map<int, std::map<int, std::string>> nested_map;

nested_map[1][3] = "toto";

int key1 = 1;
int key2 = 3;
std::string val;
auto it1 = nested_map.find(key1);
if (it1 != nested_map.end())
{
    auto it2 = it1->second.find(key2);
    if (it2 != it1->second.end())
    {
         val = it2->second;
    }
}

Edit: I am only looking for syntax sugar to save typing a bit, because there are a lot of these nested maps in my code.

Edit2: I don’t want to throw on failure.

5 Answers

What about a simple (possibly inlined) function like this:

using nested_map = std::map<int, std::map<int, std::string>>;


bool find_value(const nested_map& map, int key1, int key2, std::string& val)
{
    auto it1 = nested_map.find(key1);
    if(it1 == map.end()) return false;

    auto it2 = it1->second.find(key2);
    if(it2 == it1->second.end()) return false;
   
    val = it2->second;
    return true;
}


std::string val;
if(find_value(map, 1, 3, val))
{
    // do something useful
} 

Alternate version, which is shorter, but uses exceptions internally:

bool find_value(const nested_map& map, int key1, int key2, std::string& val)
{
    try { val = map.at(key1).at(key2); }
    catch(...) { return false; }
   
    return true;
}

Write a recursive variadic template function that would accept map by reference and keys as variadic template argument, and returnstd::optional of innermost value type. Or may return pointer with nullptr indication that it is not found.

Nothing wrong with creating functions to shorten your code (and remove duplication). Here is my take on that

#include <iostream>
#include <map>
#include <string>

using nested_map_t = std::map<int, std::map<int, std::string>>;

// helper struct, to be able to return 2 values from lookup function
struct lookup_t
{
    bool success = false;   // lookup was successful then this will become true
    std::string value;      // the lookup value (empty string by default)

    // implicit cast to bool, so we can use the return value in an if statement
    operator bool() const
    {
        return success;
    }
};

auto lookup_in(const nested_map_t& map, int key1, int key2)
{
    lookup_t result;

    auto it1 = map.find(key1);
    if (it1 != map.end())
    {
        auto it2 = it1->second.find(key2);
        if (it2 != it1->second.end())
        {
            result.value = it2->second;
            result.success = true;
        }
    }

    return result;
}

int main()
{
    nested_map_t nested_map;
    nested_map[1][3] = "toto";

    // syntactic sugar : bool cast of result
    if (auto result = lookup_in(nested_map, 1, 3))
    {
        std::cout << result.value << "\n";
    }
}

If you are not concerned with inserting empty strings for invalid keys, this will return that newly added string:

std::map<int, std::map<int, std::string>> nested_map;

nested_map[1][3] = "toto";

std::string val = nested_map[2][4];

With respect to "I don’t want to throw on failure." is that no exceptions at all or is a caught exception ok?

#include <map>
#include <string>
#include <iostream>
#include <exception>

std::string Get_Value(const std::map<int, std::map<int, std::string>> &in, int key1, int key2) {
    std::string res;
    try {
        res = in.at(key1).at(key2);
    }
    catch (const std::out_of_range &err) {
        res = std::string();
    }
    return res;     
}

int main(int argc, char * argv[]) {

    std::map<int, std::map<int, std::string>> nested_map;
    nested_map[1][3] = "toto";
    nested_map[1][2] = "tinman";
    nested_map[2][1] = "africa";

    auto res = Get_Value(nested_map, 1, 3);
    std::cout << res << std::endl;
    res = Get_Value(nested_map, 2, 1);
    std::cout << res << std::endl;
    res = Get_Value(nested_map, 1, 1);
    std::cout << res << std::endl;

    return 0;
}
Related