Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic C++ for reading from a const map

Tags:

c++

stl

For an std::map<std::string, std::string> variables, I'd like to do this:

BOOST_CHECK_EQUAL(variables["a"], "b");

The only problem is, in this context variables is const, so operator[] won't work :(

Now, there are several workarounds to this; casting away the const, using variables.count("a") ? variables.find("a")->second : std::string() or even making a function wrapping that. None of these seem to me to be as nice as operator[]. What should I do? Is there a standard way of doing this (beautifully)?

Edit: Just to state the answer that none of you want to give: No, there is no convenient, beautiful, standard way of doing this in C++. I will have to implement a support function.

like image 413
Magnus Hoff Avatar asked Sep 30 '08 11:09

Magnus Hoff


2 Answers

template <typename K, typename V>
V get(std::map<K, V> const& map, K const& key)
{
    std::map<K, V>::const_iterator iter(map.find(key));
    return iter != map.end() ? iter->second : V();
}

Improved implementation based on comments:

template <typename T>
typename T::mapped_type get(T const& map, typename T::key_type const& key)
{
    typename T::const_iterator iter(map.find(key));
    return iter != map.end() ? iter->second : typename T::mapped_type();
}
like image 72
Chris Jester-Young Avatar answered Sep 20 '22 17:09

Chris Jester-Young


Casting away const is wrong, because operator[] on map<> will create the entry if it isn't present with a default constructed string. If the map is actually in immutable storage then it will fail. This must be so because operator[] returns a non-const reference to allow assignment. (eg. m[1] = 2)

A quick free function to implement the comparison:

template<typename CONT>
bool check_equal(const CONT& m, const typename CONT::key_type& k,
                    const typename CONT::mapped_type& v)
{
    CONT::const_iterator i(m.find(k));
    if (i == m.end()) return false;
    return i->second == v;
}

I'll think about syntactic sugar and update if I think of something.

...

The immediate syntactic sugar involved a free function that does a map<>::find() and returns a special class that wraps map<>::const_iterator, and then has overloaded operator==() and operator!=() to allow comparison with the mapped type. So you can do something like:

if (nonmutating_get(m, "key") == "value") { ... }

I'm not convinced that is much better than:

if (check_equal(m, "key", "value")) { ... }

And it is certainly much more complex and what is going on is much less obvious.

The purpose of the object wrapping the iterator is to stop having default constructed data objects. If you don't care, then just use the "get" answer.

In response to the comment about the get being preferred over a comparison in the hope of finding some future use, I have these comments:

  • Say what you mean: calling a function called "check_equal" makes it clear that you are doing an equality comparison without object creation.

  • I recommend only implementing functionality once you have a need. Doing something before then is often a mistake.

  • Depending on the situation, a default constructor might have side-effects. If you are comparing, why do anything extra?

  • The SQL argument: NULL is not equivalent to an empty string. Is the absence of a key from your container really the same as the key being present in your container with a default constructed value?

Having said all that, a default constructed object is equivalent to using map<>::operator[] on a non-const container. And perhaps you have a current requirement for a get function that returns a default constructed object; I know I have had that requirement in the past.

like image 26
janm Avatar answered Sep 21 '22 17:09

janm