531

What is the right way of initializing a static map? Do we need a static function that will initialize it?

Rualark
  • 445
  • 1
  • 5
  • 18
Nithin
  • 5,331
  • 3
  • 17
  • 8

12 Answers12

734

Using C++11 initializer list {{},{},...}. The order of the initialized elements does not matter. The map will do the ordering by the key for you. If initializing unordered_map, it is the same principle where the sorted order will be determined by the hashing function and will appear to human eye as random:

#include <map>
using namespace std;

map<int, char> m = {{1, 'a'}, {3, 'b'}, {5, 'c'}, {7, 'd'}};

Using Boost.Assign:

#include <map>
#include "boost/assign.hpp"
using namespace std;
using namespace boost::assign;

map<int, char> m = map_list_of (1, 'a') (3, 'b') (5, 'c') (7, 'd');
Kemin Zhou
  • 6,264
  • 2
  • 48
  • 56
Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • 135
    Every time I see something like that done with C++, I think of all the horrendous template code that must be behind it. Good example! – Greg Hewgill Sep 26 '08 at 10:22
  • 5
    It is implemented "basically" as an overloaded operator, but if you ever have a syntax error, enjoy that line of code. It pulls in 15 different .hpp's for something that would take you a minute or two. Code for fun? Use it; otherwise, consider the cost to time/size. – hazzen Sep 26 '08 at 14:23
  • 5
    Boost is great. Wonderful stuff in there. Problem in my case : we can't use it as a company guideline. Not standard enough, not easy enough for the average developer ( not my phrasing ). – QBziZ Sep 26 '08 at 23:54
  • 38
    The beauty of all the horrendous template code that implements these utilities is that it is neatly encapsulated in a library and the end user rarely needs to deal with the complexity. – Steve Guidi Nov 13 '09 at 18:09
  • 47
    @QBziZ: If your company declines using Boost on the grounds of it not being "standard enough", I wonder what C++ library *would* be "standard enough". Boost is *the* standard companion for the C++ coder. – DevSolar Jan 05 '12 at 13:04
  • 4
    @DevSolar I would like to use boost more, but its synergy with C++11 is terrible, and there are some rather... _curious_ design decisions (Iostreams requires Devices to be copyable, but makes streams noncopyable... oh, and also nonmovable - at least in the implementation I'm using, maybe they fixed it?). – Cubic May 08 '13 at 19:16
  • 53
    My problem with Boost (here, and elsewhere) is that you can often get on without it (in this case with C++11 or before C++11 [with a function](http://stackoverflow.com/a/138633/111307)). Boost adds a significant compile time overhead, had tons of files to park into your repository (and to have to copy around/zip/extract if you are making an archive). That's the reason I try not to use it. I know you can choose what files to include/not include, but you usually don't want to have to worry about Boost's cross dependencies with itself so you just copy the whole thing around. – bobobobo Jun 01 '13 at 00:43
  • 8
    My problem with Boost is that it often has several new library dependencies, which generally means MORE packages that need to be installed to work correctly. We already need libstdc++. For example, the Boost ASIO library, requires at least 2 new libraries(probably more) that need to be installed. C++11/14 does make it a lot easier to not need Boost. – Rahly May 31 '16 at 20:42
  • @nobar apparently, duplicate keys are silently ignored, only the first instance is included. Most probablyn, I just don't have the right compiler options to generate warnings about that. – Eusebius Dec 20 '18 at 09:57
  • Why this fails to compile on vs2017 with Standard set to c++17 – att Jan 31 '20 at 10:27
139

Best way is to use a function:

#include <map>

using namespace std;

map<int,int> create_map()
{
  map<int,int> m;
  m[1] = 2;
  m[3] = 4;
  m[5] = 6;
  return m;
}

map<int,int> m = create_map();
PierreBdR
  • 42,120
  • 10
  • 46
  • 62
  • 1
    Nice, I had to just fix build error under MSVS'12, and it was really helpful; faster than setting up boost here. – Bartek Banachewicz Apr 03 '13 at 08:05
  • 26
    Why is this the 'best'? Why for example is it better than @Dreamer's answer? – user207421 May 06 '13 at 23:40
  • 7
    I think it's "best" because it's really simple and doesn't depend on other structures existing (such as the Boost::Assign or a reimplementation of it). And compared to @Dreamer's answer, well, I avoid creating a whole structure only for initializing a map ... – PierreBdR May 10 '13 at 14:54
  • 1
    Is there a __name__ for this idiom? What "time" is this `create_map` function running? It runs before `main`, what is the _name_ for that runtime? – bobobobo Jun 01 '13 at 15:56
  • 3
    [Note there is a danger here](http://bobobobo.wordpress.com/2013/06/01/extern-variable-always-0/). `extern` variables will not have their correct values in this "before main run-time constructor" _if the compiler only saw the `extern` declaration, but has not run into the actual variable definition yet_. – bobobobo Jun 01 '13 at 16:04
  • 5
    No, the danger is that there is nothing saying in which order the static variables should be initialized (at least across compilation units). But this is not a problem linked to this question. This is a general problem with static variables. – PierreBdR Jun 04 '13 at 09:06
  • How long does this m going to live? How is its memory allocated? Little bit confused as I always used @eduffy's way. – Juto Feb 16 '14 at 11:32
  • 1
    I like this answer because it's simple and complier friendly. Even with C++ 11, map m = {{1,1}, {2,2}}; will not work if your complier does not support it yet. – Pittfall Jul 11 '14 at 18:39
  • 8
    no boost AND no C++11 => +1. Notice that function can be used to initialize a `const map m = create_map()` (and so, initialize const members of a class in the initialization list: `struct MyClass {const map m; MyClass(); }; MyClass::MyClass() : m(create_map())` – ribamar Mar 13 '15 at 10:00
  • 1
    Looks like an ugly hack to me, but it does work in c++98 and that's what I searched for. – Nobody Nov 30 '16 at 17:09
117

It's not a complicated issue to make something similar to boost. Here's a class with just three functions, including the constructor, to replicate what boost did (almost).

template <typename T, typename U>
class create_map
{
private:
    std::map<T, U> m_map;
public:
    create_map(const T& key, const U& val)
    {
        m_map[key] = val;
    }

    create_map<T, U>& operator()(const T& key, const U& val)
    {
        m_map[key] = val;
        return *this;
    }

    operator std::map<T, U>()
    {
        return m_map;
    }
};

Usage:

std::map mymap = create_map<int, int >(1,2)(3,4)(5,6);

The above code works best for initialization of global variables or static members of a class which needs to be initialized and you have no idea when it gets used first but you want to assure that the values are available in it.

If say, you've got to insert elements into an existing std::map... here's another class for you.

template <typename MapType>
class map_add_values {
private:
    MapType mMap;
public:
    typedef typename MapType::key_type KeyType;
    typedef typename MapType::mapped_type MappedType;

    map_add_values(const KeyType& key, const MappedType& val)
    {
        mMap[key] = val;
    }

    map_add_values& operator()(const KeyType& key, const MappedType& val) {
        mMap[key] = val;
        return *this;
    }

    void to (MapType& map) {
        map.insert(mMap.begin(), mMap.end());
    }
};

Usage:

typedef std::map<int, int> Int2IntMap;
Int2IntMap testMap;
map_add_values<Int2IntMap>(1,2)(3,4)(5,6).to(testMap);

See it in action with GCC 4.7.2 here: http://ideone.com/3uYJiH

############### EVERYTHING BELOW THIS IS OBSOLETE #################

EDIT: The map_add_values class below, which was the original solution I had suggested, would fail when it comes to GCC 4.5+. Please look at the code above for how to add values to existing map.


template<typename T, typename U>
class map_add_values
{
private:
    std::map<T,U>& m_map;
public:
    map_add_values(std::map<T, U>& _map):m_map(_map){}
    map_add_values& operator()(const T& _key, const U& _val)
    {
        m_map[key] = val;
        return *this;
    }
};

Usage:

std::map<int, int> my_map;
// Later somewhere along the code
map_add_values<int,int>(my_map)(1,2)(3,4)(5,6);

NOTE: Previously I used a operator [] for adding the actual values. This is not possible as commented by dalle.

##################### END OF OBSOLETE SECTION #####################

Vite Falcon
  • 6,575
  • 3
  • 30
  • 48
  • 3
    I'm using your first sample as to bind error-numbers (from an enum) with messages - it is working like a charm - thank you. – slashmais Sep 22 '10 at 10:57
  • 1
    `operator[]` only takes a single argument. – dalle Jul 25 '11 at 10:35
  • 1
    @dalle: Good catch! For some reason I thought overloaded [] operators could accept more. – Vite Falcon Jul 26 '11 at 10:55
  • 3
    This is a fantastic answer. It's a shame the OP never selected one. You deserve mega props. – Thomas Thorogood Sep 26 '12 at 18:26
  • the map_add_values doesn't work in gcc, which complains: `error: conflicting declaration ‘map_add_values my_map’` `error: ‘my_map’ has a previous declaration as ‘std::map my_map’` – Martin Wang May 20 '13 at 05:25
  • @martinwang Which version of gcc? Reading the compiler error it seems like you did something like `map_add_values my_map...`. Are you using it like shown in my usage example? – Vite Falcon May 20 '13 at 13:52
  • GCC4.5.1. I have tried your example exactly. GCC is just confused by hanging anonymous object. `class A{}; A a();` is fine. `class A{}; A();` results in a compile error. – Martin Wang May 21 '13 at 01:46
  • @MartinWang, I see. This is the case where GCC thinks that the statement `map_add_values(my_map)(1,2)(3,4);` is like `map_add_values my_map(1,2)(3,4);`. I'm looking into how to come up with a an elegant solution. – Vite Falcon May 21 '13 at 03:04
  • @MartinWang, I have a newer solution. It seems to work with GCC 4.7.2 and you can see it being compiled on ideone. I have moved my original (broken) solution to the end for historical tracking. – Vite Falcon May 21 '13 at 03:18
  • Well, your example is used in main(). Hanging anonymous object is still forbidded in globle namespace. Like `class A {public: A(int){} void to(){}}; A(1).to();` – Martin Wang May 21 '13 at 05:09
  • My solusion is using a unique namespace, in which the anonymous object is assigned to a variable. For simplicity, all the code defined in a macro used as `MAP_ADD(mymap,int,int, (1,2)(3,4));` – Martin Wang May 21 '13 at 05:15
  • @MartinWang - Is it possible to pass a pointer to a structure instead of int value? map instead of map in this example? thanks – ejuser Jul 16 '14 at 06:35
  • @ejuser you can pass the type of the pointer, what's your point? – Martin Wang Jul 16 '14 at 07:49
  • @MartinWang - Actually have 2 questions related to this ( I can open another thread if needed )
    Question 1: (seperate issue ) - Unable to use the line
    std::map mymap = create_map(1,2)(3,4)(5,6);
    Question 2 : Would there be any memory leaks if do not clear up the map ? Do we need to add any specific explicit commands to clear up map (free(map))?
    Question 3 - Is there any way to expand this as an unique_pointer to a mapclass>
    – ejuser Jul 16 '14 at 13:59
  • @ejuser 1. show your code and problems. 2. no need to `delete/free` if no `new/malloc` used. 3. Arguments of template are types, not specific pointer, I don't get your point. – Martin Wang Jul 16 '14 at 16:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/57430/discussion-between-ejuser-and-martin-wang). – ejuser Jul 16 '14 at 17:35
  • Hi nice solution but I thing the map_add_values would be better this way: template class map_adder { private: MapType &mMap; public: typedef typename MapType::key_type KeyType; typedef typename MapType::mapped_type MappedType; map_adder(MapType& Map):mMap(Map) { } map_adder& operator()(const KeyType& key, const MappedType& val) { mMap[key] = val; return *this; } }; template map_adder map_add_values(Maptype& data2change) { return map_adder(data2change); }->map_add_values(mymap)(3,3); – jamk Oct 28 '14 at 09:34
46

Here is another way that uses the 2-element data constructor. No functions are needed to initialize it. There is no 3rd party code (Boost), no static functions or objects, no tricks, just simple C++:

#include <map>
#include <string>

typedef std::map<std::string, int> MyMap;

const MyMap::value_type rawData[] = {
   MyMap::value_type("hello", 42),
   MyMap::value_type("world", 88),
};
const int numElems = sizeof rawData / sizeof rawData[0];
MyMap myMap(rawData, rawData + numElems);

Since I wrote this answer C++11 is out. You can now directly initialize STL containers using the new initializer list feature:

const MyMap myMap = { {"hello", 42}, {"world", 88} };
Brian Neal
  • 31,821
  • 7
  • 55
  • 59
37

For example:

const std::map<LogLevel, const char*> g_log_levels_dsc =
{
    { LogLevel::Disabled, "[---]" },
    { LogLevel::Info,     "[inf]" },
    { LogLevel::Warning,  "[wrn]" },
    { LogLevel::Error,    "[err]" },
    { LogLevel::Debug,    "[dbg]" }
};

If map is a data member of a class, you can initialize it directly in header by the following way (since C++17):

// Example

template<>
class StringConverter<CacheMode> final
{
public:
    static auto convert(CacheMode mode) -> const std::string&
    {
        // validate...
        return s_modes.at(mode);
    }

private:
    static inline const std::map<CacheMode, std::string> s_modes =
        {
            { CacheMode::All, "All" },
            { CacheMode::Selective, "Selective" },
            { CacheMode::None, "None" }
            // etc
        };
}; 
isnullxbh
  • 807
  • 13
  • 20
26

I would wrap the map inside a static object, and put the map initialisation code in the constructor of this object, this way you are sure the map is created before the initialisation code is executed.

Drealmer
  • 5,578
  • 3
  • 30
  • 37
20

Just wanted to share a pure C++ 98 work around:

#include <map>

std::map<std::string, std::string> aka;

struct akaInit
{
    akaInit()
    {
        aka[ "George" ] = "John";
        aka[ "Joe" ] = "Al";
        aka[ "Phil" ] = "Sue";
        aka[ "Smitty" ] = "Yando";
    }
} AkaInit;
user3826594
  • 209
  • 2
  • 2
14

You can try:

std::map <int, int> mymap = 
{
        std::pair <int, int> (1, 1),
        std::pair <int, int> (2, 2),
        std::pair <int, int> (2, 2)
};
isnullxbh
  • 807
  • 13
  • 20
  • 3
    You cannot use initializer lists with non-aggregate types before C++11, in which case you may as well use the shorter syntax `{1, 2}` instead of `std::pair(1, 2)`. – Ferruccio Feb 21 '18 at 12:28
10

If you are stuck with C++98 and don't want to use boost, here there is the solution I use when I need to initialize a static map:

typedef std::pair< int, char > elemPair_t;
elemPair_t elemPairs[] = 
{
    elemPair_t( 1, 'a'), 
    elemPair_t( 3, 'b' ), 
    elemPair_t( 5, 'c' ), 
    elemPair_t( 7, 'd' )
};

const std::map< int, char > myMap( &elemPairs[ 0 ], &elemPairs[ sizeof( elemPairs ) / sizeof( elemPairs[ 0 ] ) ] );
8

This is similar to PierreBdR, without copying the map.

#include <map>

using namespace std;

bool create_map(map<int,int> &m)
{
  m[1] = 2;
  m[3] = 4;
  m[5] = 6;
  return true;
}

static map<int,int> m;
static bool _dummy = create_map (m);
eduffy
  • 39,140
  • 13
  • 95
  • 92
2

In addition to the good top answer of using

const std::map<int, int> m = {{1,1},{4,2},{9,3},{16,4},{32,9}}

there's an additional possibility by directly calling a lambda that can be useful in a few cases:

const std::map<int, int> m = []()->auto {
  std::map<int, int> m;
  m[1]=1;
  m[4]=2;
  m[9]=3;
  m[16]=4;
  m[32]=9;
  return m;
}();

Clearly a simple initializer list is better when writing this from scratch with literal values, but it does open up additional possibilities:

const std::map<int, int> m = []()->auto {
  std::map<int, int> m;
  for(int i=1;i<5;++i) m[i*i]=i;
  m[32]=9;
  return m;
}();

(Obviously it should be a normal function if you want to re-use it; and this does require recent C++.)

Hans Olsson
  • 11,123
  • 15
  • 38
-7

You have some very good answers here, but I'm to me, it looks like a case of "when all you know is a hammer"...

The simplest answer of to why there is no standard way to initialise a static map, is there is no good reason to ever use a static map...

A map is a structure designed for fast lookup, of an unknown set of elements. If you know the elements before hand, simply use a C-array. Enter the values in a sorted manner, or run sort on them, if you can't do this. You can then get log(n) performance by using the stl::functions to loop-up entries, lower_bound/upper_bound. When I have tested this previously they normally perform at least 4 times faster than a map.

The advantages are many fold... - faster performance (*4, I've measured on many CPU's types, it's always around 4) - simpler debugging. It's just easier to see what's going on with a linear layout. - Trivial implementations of copy operations, should that become necessary. - It allocates no memory at run time, so will never throw an exception. - It's a standard interface, and so is very easy to share across, DLL's, or languages, etc.

I could go on, but if you want more, why not look at Stroustrup's many blogs on the subject.

  • 8
    Performance is not the only reason for using a map. For example, there are many cases, where you want to link values together (for example, an error code with an error message), and a map makes the use and access relatively simple. But a link to these blog entries may be interesting, maybe I'm doing something wrong. – MatthiasB Jul 21 '14 at 12:59
  • 5
    An array is much easier and has higher performance if you can use it. But if the indices (keys) are not contiguous, and widely spaced, you need a map. – KarlU Oct 28 '14 at 16:48
  • 1
    A `map` is also a useful form for representing a partial function (function in the mathematical sense; but also, kind of, in the programming sense). An array does not do that. You can't, say, lookup data from an array using a string. – einpoklum Jul 20 '15 at 19:21
  • 3
    Your answer does not attempt to answer the valid question, and instead speculates on the limitations of the language, proposes solutions to different problems, hence downvote. A real scenario - mapping (continuous or not) library error codes to text strings. With array, search time is O(n), which can be improved by static maping to O(log(n)). – Tosha Jan 22 '16 at 11:41
  • 2
    If indeed "there is no good reason to ever use a static map..." then it is very strange that syntax (initializer lists) that make them easy to use was added in C++11. – ellisbben Jul 01 '16 at 15:09
  • @Tosha: I think the answer addresses your scenario and describes how it could be searched in O(log(n)) without using a map. See [Where can I get a “useful” C++ binary search algorithm?](http://stackoverflow.com/q/446296/86967). – Brent Bradburn Aug 17 '16 at 04:40
  • Valid use case: to feed a static map to code that normally uses a dynamic one. I'm sure there are others. It's usually a good idea to reconsider whenever one finds oneself making a sweeping generalization. – Jeff Learman Apr 11 '21 at 16:32