12

In Python, there is a class called defaultdict which is essentially a dictionary that will construct elements on demand according to a function specified by the user at construction time..

Does a similar class already exists in C++, or would I have to create it myself by inheriting from map and overwriting the at method?

merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • See this [post](http://stackoverflow.com/a/1842976/1982962) – Kobi K Oct 14 '13 at 07:06
  • 1
    I think the best way is to create a class that wraps `std::map` (or `std::unordered_map`). See also http://stackoverflow.com/questions/922248/is-there-any-real-risk-to-deriving-from-the-c-stl-containers – Jim Garrison Oct 14 '13 at 07:13
  • 2
    Do you need a specific function to create the elements or are you OK with default constructed values (e.g. 0). In that case the [] operator already does that. – dornhege Oct 14 '13 at 07:14
  • I need a specific function. – merlin2011 Oct 14 '13 at 07:38
  • 1
    You should not use inheritance. Just write a function that does the work for you, or a class that *has-a* map, and not *is-a* map. – juanchopanza Oct 14 '13 at 07:45
  • In my particular use case, that should work, but in the general case, how would I expose all the features of an stl `map` in addition to the extra feature without writing a whole bunch boilerplate wrapper methods around every method of `map`? That is, suppose I needed a full `std::map`, just with the ability to construct elements on demand. – merlin2011 Oct 14 '13 at 07:52
  • Actually `std::map` **is** a `defaultdict` – or rather, its behaviour is to default-construct missing items, in contrast with Python’s default `dict` behaviour. Of course `defaultdict` allows parametrisation beyond that, but maybe you don’t need this … All you have to do is use `operator[]` instead of `at`. – Konrad Rudolph Oct 14 '13 at 08:01
  • @merlin2011 See my answer for that - inherit non-publically and publish things you need with `using` declarations. It's a bit of boilerplate, but much less than writing wrappers. – Angew is no longer proud of SO Oct 14 '13 at 08:08

2 Answers2

9

This is not directly an answer to your question, but if you want the same behavior as defaultdict for aggregation, you could use map.emplace to assign a default value if the key does not exist, and return an iterator to the new or existing item (which avoids a second lookup):

unordered_map<int, size_t> map = {{1, 1}, {2, 3}};
// later...
for (int i = 1; i < 4; i++) {
    auto emplace_pair = map.emplace(i, 0);
    emplace_pair.first->second += 1;
}
17andLearning
  • 477
  • 1
  • 8
  • 19
5

There's nothing in the standard library that would do exactly what you want, you'll have to provide such a class yourself.

However, please note that it's a bad idea to publically inherit from a standard library container (such as std::map); they are not designed for this, they don't have virtual functions and they don't have a virtual destructor. Consider this example to see why it's a bad idea:

template <class K, class V, class C, class A>
void foo(const std::map<K, V, C, A> &arg)
{
  doSomething(arg.at(K()));
}

struct MyMap : std::map<int, int>
{
  int at(int) { return 7; }
};

int main()
{
  MyMap m;
  foo(m);  //this will call std::map::at, NOT MyMap::at
}

Instead, have your class store a std::map (or perhaps std::unordered_map, whichever is better for your implementation) by value. Or, if you think you could re-use a lot of the standard map's member functions and only override some, you could inherit from it non-publically and publish only the functions you need. Example:

template <
  class Key,
  class Value,
  class Comparator = typename std::map<Key, Value>::key_compare,
  class Allocator = typename std::map<Key, Value>::allocator_type
>
class DefaultDict : private std::map<Key, Value, Comparator, Allocator>
{
public:
  // Publish the clear() function as is
  using std::map<Key, Value, Comparator, Allocator>::clear;

  // Provide my own at()
  Value& at(const Key &key) {
    return std::map<Key, Value, Comparator, Allocator>::operator[](key); //call the inherited function
  }

  // Etc.
};
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455