124

Is there a way to specify the default value std::map's operator[] returns when an key does not exist?

Sergey K.
  • 24,894
  • 13
  • 106
  • 174
anon
  • 41,035
  • 53
  • 197
  • 293

16 Answers16

69

While this does not exactly answer the question, I have circumvented the problem with code like this:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

std::map<std::string, IntDefaultedToMinusOne > mymap;
SurvivalMachine
  • 7,946
  • 15
  • 57
  • 87
  • 4
    This is the best solution for me. Easy to implement, very flexible, and generic. – acegs Apr 08 '19 at 01:36
  • 5
    note you can add `operator int() const {return i;}`, which allows you to treat it as an `int` in most cases. – Mooing Duck Mar 23 '21 at 22:10
  • 2
    @acegs You could make it actually generic: `template struct default { T t; };` Then you can think about adding conversion operator and converting constructor – Joshua Hyatt Mar 23 '21 at 22:11
68

No, there isn't. The simplest solution is to write your own free template function to do this. Something like:

#include <string>
#include <map>
using namespace std;

template <typename K, typename V>
V GetWithDef(const  std::map <K,V> & m, const K & key, const V & defval ) {
   typename std::map<K,V>::const_iterator it = m.find( key );
   if ( it == m.end() ) {
      return defval;
   }
   else {
      return it->second;
   }
}

int main() {
   map <string,int> x;
   ...
   int i = GetWithDef( x, string("foo"), 42 );
}

C++11 Update

Purpose: Account for generic associative containers, as well as optional comparator and allocator parameters.

template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
    typename C<K,V,Args...>::const_iterator it = m.find( key );
    if (it == m.end())
        return defval;
    return it->second;
}
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • 1
    Nice solution. You might want to add a few template arguments so that the function template works with maps that don't use the default template parameters for comparator and allocator. – sbi Feb 25 '10 at 12:19
  • 3
    +1, but to provide the exact same behavior as the `operator[]` with default value, the default value should be inserted into the map inside the `if ( it == m.end() )` block – David Rodríguez - dribeas Feb 25 '10 at 12:19
  • 14
    @David I'm assuming the OP doesn't actually want that behaviour. I use a similar scheme for reading configurations, but I don't want the configuration updated if a key is missing. –  Feb 25 '10 at 12:22
  • @David: I agree with Neil, but perhaps the best solution is having a parameter `doInsert` that defaults to false. When true it'll do the insert. – GManNickG Feb 25 '10 at 16:01
  • @GMan Boolean parameters? If the Brotherhood of API Purity get to hear about this, there'll be trouble! –  Feb 25 '10 at 16:54
  • 2
    @GMan bool parameters are thought by some to be bad style because you can't tell by looking at the call (as opposed to the declaration) what they do - in this case does "true" mean "use default" or "don't use default" (or something else entirely)? An enum is always clearer but of course is more code. I'm in two minds on the subject, myself. –  Feb 25 '10 at 18:48
  • @Neil: Oh I see, makes sense. I think I'm torn on it too, then. – GManNickG Feb 25 '10 at 20:01
  • 2
    This answer doesn't work if the default value is nullptr, but http://stackoverflow.com/a/26958878/297451 does. – Jon Feb 09 '16 at 05:02
  • 1
    why no auto in your C++11 update. The definition of "it" is a prime candidate isn't it? – Jesse Pepper Oct 04 '18 at 07:43
14

C++17 provides try_emplace which does exactly this. It takes a key and an argument list for the value constructor and returns a pair: an iterator and a bool.: http://en.cppreference.com/w/cpp/container/map/try_emplace

Ben
  • 9,184
  • 1
  • 43
  • 56
  • That's not exactly the same thing, since it modifies the map. – Bex Feb 28 '22 at 11:37
  • 1
    Isn't it? The question was "Is there a way to specify the default value `std::map`'s `operator[]` returns when an key does not exist?" That implies they are calling the non-`const` version of `operator[]` which does (potentially) modify the map. – Ben Feb 28 '22 at 17:58
  • 1
    @Ben : IMHO the OP did not intend actually to modify the map but rather to have an equivalent of python Dict get(key,defaultValue). Definitly there is a missing API in the std to provides an overload at function for such case – sandwood Nov 23 '22 at 22:54
11

The C++ standard (23.3.1.2) specifies that the newly inserted value is default constructed, so map itself doesn't provide a way of doing it. Your choices are:

  • Give the value type a default constructor that initialises it to the value you want, or
  • Wrap the map in your own class that provides a default value and implements operator[] to insert that default.
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 9
    Well, to be precise the newly inserted value is value initialized (8.5.5) so: - if T is a class type with a user-declared constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor); — if T is a non-union class type without a user-declared constructor, then every non-static data member and baseclass component of T is value-initialized; — if T is an array type, then each element is value-initialized; — otherwise, the object is zero-initialized – Tadeusz Kopec for Ukraine Feb 25 '10 at 12:28
8

The value is initialized using the default constructor, as the other answers say. However, it is useful to add that in case of simple types (integral types such as int, float, pointer or POD (plan old data) types), the values are zero-initialized (or zeroed by value-initialization (which is effectively the same thing), depending on which version of C++ is used).

Anyway, the bottomline is, that maps with simple types will zero-initialize the new items automatically. So in some cases, there is no need to worry about explicitly specifying the default initial value.

std::map<int, char*> map;
typedef char *P;
char *p = map[123],
    *p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0

See Do the parentheses after the type name make a difference with new? for more details on the matter.

Community
  • 1
  • 1
the swine
  • 10,713
  • 7
  • 58
  • 100
6

There is no way to specify the default value - it is always value constructed by the default (zero parameter constructor).

In fact operator[] probably does more than you expect as if a value does not exist for the given key in the map it will insert a new one with the value from the default constructor.

Ross Rogers
  • 23,523
  • 27
  • 108
  • 164
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • 2
    Right, to avoid adding new entries you could use `find` which does return the end iterator if no element exists for a given key. – Thomas Schaub Feb 25 '10 at 12:05
  • @ThomasSchaub How much is the time complexity of `find` in that case? – asn Jul 29 '19 at 15:46
5
template<typename T, T X>
struct Default {
    Default () : val(T(X)) {}
    Default (T const & val) : val(val) {}
    operator T & () { return val; }
    operator T const & () const { return val; }
    T val;
};

<...>

std::map<KeyType, Default<ValueType, DefaultValue> > mapping;
Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
4

More General Version, Support C++98/03 and More Containers

Works with generic associative containers, the only template parameter is the container type itself.

Supported containers: std::map, std::multimap, std::unordered_map, std::unordered_multimap, wxHashMap, QMap, QMultiMap, QHash, QMultiHash, etc.

template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m, 
                                             const typename MAP::key_type& key, 
                                             const typename MAP::mapped_type& defval)
{
    typename MAP::const_iterator it = m.find(key);
    if (it == m.end())
        return defval;

    return it->second;
}

Usage:

std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");

Here is a similar implementation by using a wrapper class, which is more similar to the method get() of dict type in Python: https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp

template<typename MAP>
struct map_wrapper
{
    typedef typename MAP::key_type K;
    typedef typename MAP::mapped_type V;
    typedef typename MAP::const_iterator CIT;

    map_wrapper(const MAP& m) :m_map(m) {}

    const V& get(const K& key, const V& default_val) const
    {
        CIT it = m_map.find(key);
        if (it == m_map.end())
            return default_val;

        return it->second;
    }
private:
    const MAP& m_map;
};

template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
    return map_wrapper<MAP>(m);
}

Usage:

std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
jyw
  • 135
  • 1
  • 3
  • 2
    MAP::mapped_type& is not safe to return since typename MAP::mapped_type& defval can be out of scope. – Joe C Aug 17 '19 at 02:31
2

One workaround is to use map::at() instead of []. If a key does not exist, at throws an exception. Even nicer, this also works for vectors, and is thus suited for generic programming where you may swap the map with a vector.

Using a custom value for unregistered key may be dangerous since that custom value (like -1) may be processed further down in the code. With exceptions, it's easier to spot bugs.

Dean
  • 537
  • 4
  • 8
2

Pre-C++17, use std::map::insert(), for newer versions use try_emplace(). It may be counter-intuitive, but these functions effectively have the behaviour of operator[] with custom default values.

Realizing that I'm quite late to this party, but if you're interested in the behaviour of operator[] with custom defaults (that is: find the element with the given key, if it isn't present insert a chosen default value and return a reference to either the newly inserted value or the existing value), there is already a function available to you pre C++17: std::map::insert(). insert will not actually insert if the key already exists, but instead return an iterator to the existing value.

Say, you wanted a map of string-to-int and insert a default value of 42 if the key wasn't present yet:

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

int count_answers( const std::string &question)
{
    auto  &value = answers.insert( {question, 42}).first->second;
    return value++;
}

int main() {

    std::cout << count_answers( "Life, the universe and everything") << '\n';
    std::cout << count_answers( "Life, the universe and everything") << '\n';
    std::cout << count_answers( "Life, the universe and everything") << '\n';
    return 0;
}

which should output 42, 43 and 44.

If the cost of constructing the map value is high (if either copying/moving the key or the value type is expensive), this comes at a significant performance penalty, which would be circumvented with C++17's try_emplace().

dhavenith
  • 2,028
  • 13
  • 14
2

Expanding on the answer https://stackoverflow.com/a/2333816/272642, this template function uses std::map's key_type and mapped_type typedefs to deduce the type of key and def. This doesn't work with containers without these typedefs.

template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
    typename C::const_iterator it = m.find(key);
    if (it == m.end())
        return def;
    return it->second;
}

This allows you to use

std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);

without needing to cast the arguments like std::string("a"), (int*) NULL.

Vortico
  • 2,610
  • 2
  • 32
  • 49
2

If you have access to C++17, my solution is as follows:

std::map<std::string, std::optional<int>> myNullables;
std::cout << myNullables["empty-key"].value_or(-1) << std::endl;

This allows you to specify a 'default value' at each use of the map. This may not necessarily be what you want or need, but I'll post it here for the sake of completeness. This solution lends itself well to a functional paradigm, as maps (and dictionaries) are often used with such a style anyway:

Map<String, int> myNullables;
print(myNullables["empty-key"] ?? -1);
Joshua Hyatt
  • 1,300
  • 2
  • 11
  • 22
  • This assumes the map itself contains raw optionals, which isn't always the case. – Zoe May 28 '21 at 16:08
  • @Zoe I'm not sure what you're suggesting. My solution is to create a map using an optional of T as the value. If you are writing the code from scratch (or modifying existing code), you can create a map of raw optionals any time you like (with some restrictions, e.g. no optional references) – Joshua Hyatt May 31 '21 at 16:18
  • But it doesn't work if the map is out of your control - that was my point. It's not a general solution that works for any arbitrary map – Zoe May 31 '21 at 16:22
  • @Zoe Ah, now I understand. Thank you for the input and clarification. – Joshua Hyatt Jun 03 '21 at 16:36
1

Maybe you can give a custom allocator who allocate with a default value you want.

template < class Key, class T, class Compare = less<Key>,
       class Allocator = allocator<pair<const Key,T> > > class map;
VDVLeon
  • 1,393
  • 1
  • 15
  • 26
  • 4
    `operator[]` returns an object created by invoking `T()`, no matter what the allocator does. – sbi Feb 25 '10 at 12:17
  • 1
    @sbi: Doesn't the map call the allocators `construct` method? It would be possible to change that, I think. I suspect a `construct` function that doesn something other than `new(p) T(t);` isn't well-formed, though. EDIT: In hindsight that was foolish, otherwise all the values would be the same :P Where's my coffee... – GManNickG Feb 25 '10 at 16:04
  • 1
    @GMan: my copy of C++03 says (in 23.3.1.2) that `operator[]` returns `(*((insert(make_pair(x, T()))).first)).second`. So unless I'm missing something, this answer is wrong. – sbi Feb 26 '10 at 12:44
  • You are right. But that seems wrong to me. Why dont they use the allocator function for inserting? – VDVLeon Feb 26 '10 at 16:12
  • 2
    @sbi: No, I agree this answer is wrong, but for a different reason. The compiler indeed does `insert` with a `T()`, but inside insert is when it will use the allocator get memory for a new `T` then call `construct` on that memory with the parameter it was given, which is `T()`. So it is indeed possible to change the behavior of `operator[]` to have it return something else, but the allocator cannot differentiate why it's being called. So even if we made `construct` ignore it's parameter and use our special value, that would mean *every* constructed element had that value, which is bad. – GManNickG Feb 26 '10 at 19:10
  • If you use an allocator to alter []'s behavior, you should keep in mind that T() will be called after allocation. So it's quite useless when used with classes that have their T() constructors. IMHO this approach is the only legal way to give default values for the primitive types like int or double. – Dmitriy Yurchenko Feb 14 '13 at 23:14
1

With C++20 it is simple to write such getter:

constexpr auto &getOrDefault(const auto &map, const auto &key, const auto &defaultValue)
{
    const auto itr = map.find(key);
    return itr == map.cend() ? defaultValue : itr->second;
}
Childcity
  • 19
  • 7
  • 1
    If someone says `auto& v = getOrDefault(m, badKey, "foo");`, is it possible to end up with a reference to the destroyed temporary `"foo"`? Since we're returning a reference to `defaultValue`? – 425nesp Apr 21 '22 at 15:06
  • @425nesp No, this is not possible, because a compiler must not compile such case – Childcity May 23 '22 at 22:30
  • The standard says: `The requirements for constant expressions do not currently, but should, exclude expressions that have undefined behavior...`[link](https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1313) – Childcity May 23 '22 at 22:36
  • 1
    @Childcity the question isn't only about constant expressions. constexpr can be invoked as regular functions - in that case undefined expression isn't excluded, – Кое Кто Jan 25 '23 at 12:08
0

Here is a correct approach that will conditionally return a reference if the caller passes in an lvalue reference to the mapped type.

template <typename Map, typename DefVal>
using get_default_return_t = std::conditional_t<std::is_same_v<std::decay_t<DefVal>,
    typename Map::mapped_type> && std::is_lvalue_reference_v<DefVal>,
    const typename Map::mapped_type&, typename Map::mapped_type>;

template <typename Map, typename Key, typename DefVal>
get_default_return_t<Map, DefVal> get_default(const Map& map, const Key& key, DefVal&& defval)
{
    auto i = map.find(key);
    return i != map.end() ? i->second : defval;
}

int main()
{
    std::map<std::string, std::string> map;
    const char cstr[] = "world";
    std::string str = "world";
    auto& ref = get_default(map, "hello", str);
    auto& ref2 = get_default(map, "hello", std::string{"world"}); // fails to compile
    auto& ref3 = get_default(map, "hello", cstr); // fails to compile
    return 0;
}
MarkB
  • 672
  • 2
  • 9
-1

If you would like to keep using operator[] just like when you don't have to specify a default value other than what comes out from T() (where T is the value type), you can inherit T and specify a different default value in the constructor:

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

int main() {
  class string_with_my_default : public std::string {
  public:
    string_with_my_default() : std::string("my default") {}
  };

  std::map<std::string, string_with_my_default> m;

  std::cout << m["first-key"] << std::endl;
}

However, if T is a primitive type, try this:

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

template <int default_val>
class int_with_my_default {
private:
  int val = default_val;
public:
  operator int &() { return val; }
  int* operator &() { return &val; }
};

int main() {
  std::map<std::string, int_with_my_default<1> > m;

  std::cout << m["first-key"] << std::endl;
  ++ m["second-key"];
  std::cout << m["second-key"] << std::endl;
}

See also C++ Class wrapper around fundamental types

xuhdev
  • 8,018
  • 2
  • 41
  • 69