128

I need just dictionary or associative array string => int.

There is type map C++ for this case.

But I need only one map forall instances(-> static) and this map can't be changed(-> const);

I have found this way with boost library

 std::map<int, char> example = 
      boost::assign::map_list_of(1, 'a') (2, 'b') (3, 'c');

Is there other solution without this lib? I have tried something like this, but there are always some issues with map initialization.

class myClass{
private:
    static map<int,int> create_map()
        {
          map<int,int> m;
          m[1] = 2;
          m[3] = 4;
          m[5] = 6;
          return m;
        }
    static map<int,int> myMap =  create_map();

};
Meloun
  • 13,601
  • 17
  • 64
  • 93

11 Answers11

138

The C++11 standard introduced uniform initialization which makes this much simpler if your compiler supports it:

//myClass.hpp
class myClass {
  private:
    static map<int,int> myMap;
};


//myClass.cpp
map<int,int> myClass::myMap = {
   {1, 2},
   {3, 4},
   {5, 6}
};

See also this section from Professional C++, on unordered_maps.

David C. Bishop
  • 6,437
  • 3
  • 28
  • 22
  • Do we need the equal sign at the cpp file at all? – phoad Apr 21 '17 at 00:05
  • @phoad: The equal sign is superfluous. – Jinxed Oct 06 '17 at 17:01
  • Thank you for showing the usage. It was really helpful to understand how to modify the static variables. – User9102d82 Jun 09 '18 at 23:48
  • perfect for making a `const` lookup table based on a third-party API consisting of `#define` constants for the keys, ensuring there are no duplicate keys – nmz787 Feb 19 '21 at 22:05
  • 1
    The only problem that it is not `const`. You can define it in class as `static const` and in `cpp` as `const map<...>` but to access that you need to use `at()` instead of `[ ]`. – Dmitry Oct 20 '21 at 16:14
120
#include <map>
using namespace std;

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

};

const map<int,int> A:: myMap =  A::create_map();

int main() {
}
  • 3
    +1 for simplicity, of course using a `Boost.Assign` like design is pretty neat too :) – Matthieu M. Apr 14 '10 at 11:33
  • 5
    +1, thanks. Note: I had to put the initialization line in my implementation file; leaving it in the header file gave me errors due to multiple definitions (initialization code would run whenever header was included somewhere). – System.Cats.Lol Dec 03 '12 at 19:00
  • 1
    With g++ v4.7.3, this compiles, until I add `cout << A::myMap[1];` into `main()`. It gives an error. The error doesn't occur if I remove the `const` qualifiers, so I guess map's `operator[]` can't handle a `const map`, at least, not in the g++ implementation of the C++ library. – Craig McQueen Oct 31 '13 at 01:08
  • 2
    Error is: `const_map.cpp:22:23: error: passing ‘const std::map’ as ‘this’ argument of ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&) [with _Key = int; _Tp = int; _Compare = std::less; _Alloc = std::allocator >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = int; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’ discards qualifiers [-fpermissive]` – Craig McQueen Oct 31 '13 at 01:09
  • 7
    Indeed, the map's operator[] can't operate on a const map because that operator creates the referenced entry if it doesn't exist (since it returns a reference to the mapped value). C++11 introduced the at(KeyValT key) method that lets you access the item with a given key, throwing an exception if it doesn't exist. (http://en.cppreference.com/w/cpp/container/map/at) This method will work on const instances but cannot be used to insert an element on an non-const instance (as does the [] operator). – mbargiel Feb 13 '14 at 17:01
  • 1
    This is pretty good, but I'm withholding my +1 because the solution doesn't keep the map private. Is there a way to do this without exposing the map to subclasses or meddlesome users? – Benjamin Dec 29 '15 at 18:58
12

Works fine without C++11

class MyClass {
    typedef std::map<std::string, int> MyMap;
    
    struct T {
        const char* Name;
        int Num;
    
        operator MyMap::value_type() const {
            return std::pair<std::string, int>(Name, Num);
        }
    };

    static const T MapPairs[];
    static const MyMap TheMap;
};

const MyClass::T MyClass::MapPairs[] = {
    { "Jan", 1 }, { "Feb", 2 }, { "Mar", 3 }
};

const MyClass::MyMap MyClass::TheMap(MapPairs, MapPairs + 3);
user2622030
  • 121
  • 1
  • 4
12

If you find boost::assign::map_list_of useful, but can't use it for some reason, you could write your own:

template<class K, class V>
struct map_list_of_type {
  typedef std::map<K, V> Map;
  Map data;
  map_list_of_type(K k, V v) { data[k] = v; }
  map_list_of_type& operator()(K k, V v) { data[k] = v; return *this; }
  operator Map const&() const { return data; }
};
template<class K, class V>
map_list_of_type<K, V> my_map_list_of(K k, V v) {
  return map_list_of_type<K, V>(k, v);
}

int main() {
  std::map<int, char> example = 
    my_map_list_of(1, 'a') (2, 'b') (3, 'c');
  cout << example << '\n';
}

It's useful to know how such things work, especially when they're so short, but in this case I'd use a function:

a.hpp

struct A {
  static map<int, int> const m;
};

a.cpp

namespace {
map<int,int> create_map() {
  map<int, int> m;
  m[1] = 2; // etc.
  return m;
}
}

map<int, int> const A::m = create_map();
Yu Hao
  • 119,891
  • 44
  • 235
  • 294
7

If the map is to contain only entries that are known at compile time and the keys to the map are integers, then you do not need to use a map at all.

char get_value(int key)
{
    switch (key)
    {
        case 1:
            return 'a';
        case 2:
            return 'b';
        case 3:
            return 'c';
        default:
            // Do whatever is appropriate when the key is not valid
    }
}
Matthew T. Staebler
  • 4,756
  • 19
  • 21
  • 6
    +1 for pointing out that a map is not needed, however, you can't iterate over this – Viktor Sehr Apr 14 '10 at 11:19
  • 4
    That `switch` is awful, though. Why not `return key + 'a' - 1`? – johnsyweb May 28 '10 at 00:19
  • 12
    @Johnsyweb. I assume that the mapping supplied by the original poster was presented solely as an example and not indicative of the actual mapping that he has. Therefore, I would also assume that `return key + 'a' - 1` would not work for his actual mapping. – Matthew T. Staebler Jun 07 '10 at 15:21
5

A different approach to the problem:

struct A {
    static const map<int, string> * singleton_map() {
        static map<int, string>* m = NULL;
        if (!m) {
            m = new map<int, string>;
            m[42] = "42"
            // ... other initializations
        }
        return m;
    }

    // rest of the class
}

This is more efficient, as there is no one-type copy from stack to heap (including constructor, destructors on all elements). Whether this matters or not depends on your use case. Does not matter with strings! (but you may or may not find this version "cleaner")

ypnos
  • 50,202
  • 14
  • 95
  • 141
3

You could try this:

MyClass.h

class MyClass {
private:
    static const std::map<key, value> m_myMap; 
    static const std::map<key, value> createMyStaticConstantMap();
public:
    static std::map<key, value> getMyConstantStaticMap( return m_myMap );
}; //MyClass

MyClass.cpp

#include "MyClass.h"

const std::map<key, value> MyClass::m_myMap = MyClass::createMyStaticConstantMap();

const std::map<key, value> MyClass::createMyStaticConstantMap() {
    std::map<key, value> mMap;
    mMap.insert( std::make_pair( key1, value1 ) );
    mMap.insert( std::make_pair( key2, value2 ) );
    // ....
    mMap.insert( std::make_pair( lastKey, lastValue ) ); 
    return mMap;
} // createMyStaticConstantMap

With this implementation your classes constant static map is a private member and can be accessible to other classes using a public get method. Otherwise since it is constant and can not change, you can remove the public get method and move the map variable into the classes public section. I would however leave the createMap method private or protected if inheritance and or polymorphism is required. Here are some samples of use.

 std::map<key,value> m1 = MyClass::getMyMap();
 // then do work on m1 or
 unsigned index = some predetermined value
 MyClass::getMyMap().at( index ); // As long as index is valid this will 
 // retun map.second or map->second value so if in this case key is an
 // unsigned and value is a std::string then you could do
 std::cout << std::string( MyClass::getMyMap().at( some index that exists in map ) ); 
// and it will print out to the console the string locted in the map at this index. 
//You can do this before any class object is instantiated or declared. 

 //If you are using a pointer to your class such as:
 std::shared_ptr<MyClass> || std::unique_ptr<MyClass>
 // Then it would look like this:
 pMyClass->getMyMap().at( index ); // And Will do the same as above
 // Even if you have not yet called the std pointer's reset method on
 // this class object. 

 // This will only work on static methods only, and all data in static methods must be available first.

I had edited my original post, there was nothing wrong with the original code in which I posted for it compiled, built and ran correctly, it was just that my first version I presented as an answer the map was declared as public and the map was const but wasn't static.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
2

If you are using a compiler which still doesn't support universal initialization or you have reservation in using Boost, another possible alternative would be as follows

std::map<int, int> m = [] () {
    std::pair<int,int> _m[] = {
        std::make_pair(1 , sizeof(2)),
        std::make_pair(3 , sizeof(4)),
        std::make_pair(5 , sizeof(6))};
    std::map<int, int> m;
    for (auto data: _m)
    {
        m[data.first] = data.second;
    }
    return m;
}();
Abhijit
  • 62,056
  • 18
  • 131
  • 204
1

You can use the singleton pattern for this.

// The static pointer is initialized exactly once which ensures that 
// there is exactly one copy of the map in the program, it will be 
// initialized prior to the first access, and it will not be destroyed 
// while the program is running.
class myClass {
  private:
  static std::map<int,int> myMap() {
    static const auto* myMap = new std::map<int,int> {
      {1, 2},
      {3, 4},
      {5, 6}
    };
    return *myMap;
  }
}

You can then use your map like this

int x = myMap()[i] //where i is a key in the map
Jesse de gans
  • 1,432
  • 1
  • 14
  • 27
0

A function call cannot appear in a constant expression.

try this: (just an example)

#include <map>
#include <iostream>

using std::map;
using std::cout;

class myClass{
 public:
 static map<int,int> create_map()
    {
      map<int,int> m;
      m[1] = 2;
      m[3] = 4;
      m[5] = 6;
      return m;
    }
 const static map<int,int> myMap;

};
const map<int,int>myClass::myMap =  create_map();

int main(){

   map<int,int> t=myClass::create_map();
   std::cout<<t[1]; //prints 2
}
Prasoon Saurav
  • 91,295
  • 49
  • 239
  • 345
  • 6
    A function can certainly be used to initialise a const object. –  Apr 14 '10 at 09:52
  • In OP's code `static map myMap = create_map();` is incorrect. – Prasoon Saurav Apr 14 '10 at 09:54
  • 3
    The code in the question is wrong, we all agree to that, but it has nothing to do with 'constant expressions' as you say in this answer, but rather with the fact that you can only initialize constant static members of a class in the declaration if they are of integer or enum type. For all other types, the initialization must be done in the member definition and not the declaration. – David Rodríguez - dribeas Apr 14 '10 at 10:07
  • Neil's answer compiles with g++. Still, I remember having some problems with this approach in earlier versions of GNU toolchain. Is there an universal right answer? – Basilevs Apr 14 '10 at 10:07
  • @Prasoon, Right. The issue with the OP's code was the illegal use of an initializer within the class definition. An initializer is only legal if it is an integral constant expression and it is initializing a const integral or constant enumeration type. [Section 9.4.2 of C++ standard] – Matthew T. Staebler Apr 14 '10 at 10:08
  • VS6 hates int type being initialized in such declaration. – Basilevs Apr 14 '10 at 10:09
  • @Basilevs: C and C++ have different initialization rules; was that the issue you remember with gcc? VC6 was prestandard is horribly outdated now. –  Apr 14 '10 at 10:11
  • @Basilevs: Standard C++ hates VS6 and VS6 hates Standard C++... not that much you can do there. – David Rodríguez - dribeas Apr 14 '10 at 10:12
  • @David: My statement was just in relation with the above code. Try compiling OP's code and observe the errors and please tell me what am I missing? I know what Neil is saying is perfectly correct but that has nothing to do with the code in concern – Prasoon Saurav Apr 14 '10 at 11:18
  • 1
    @Prasoon: I don't know what the compiler says, but the error in the question code is initializing a constant member attribute of class type in the class declaration, regardless of whether the initialization is a constant expression or not. If you define a class: `struct testdata { testdata(int){} }; struct test { static const testdata td = 5; }; testdata test::td;` it will fail to compile even if the initialization is performed with a constant expression (`5`). That is, 'constant expression' is irrelevant to the correctness (or lack of it) of the initial code. – David Rodríguez - dribeas Apr 14 '10 at 12:05
  • @David: You are correct that the original code has an error pertaining to initializing a constant member attribute for a non-integral type in the class declaration. However, it also has an error pertaining to using a function call where an integral constant-expression is required. Regardless of whether the type of myMap is a class type or an integral type, it cannot be initialized via a function call. If you define the class: `struct test { static int get_map() { return 5; } static const int myMap = get_map(); };` it will fail to compile even if the type of `myMap` is an integral type. – Matthew T. Staebler Apr 14 '10 at 21:12
-2

I often use this pattern and recommend you to use it as well:

class MyMap : public std::map<int, int>
{
public:
    MyMap()
    {
        //either
        insert(make_pair(1, 2));
        insert(make_pair(3, 4));
        insert(make_pair(5, 6));
        //or
        (*this)[1] = 2;
        (*this)[3] = 4;
        (*this)[5] = 6;
    }
} const static my_map;

Sure it is not very readable, but without other libs it is best we can do. Also there won't be any redundant operations like copying from one map to another like in your attempt.

This is even more useful inside of functions: Instead of:

void foo()
{
   static bool initComplete = false;
   static Map map;
   if (!initComplete)
   {
      initComplete = true;
      map= ...;
   }
}

Use the following:

void bar()
{
    struct MyMap : Map
    {
      MyMap()
      {
         ...
      }
    } static mymap;
}

Not only you don't need here to deal with boolean variable anymore, you won't have hidden global variable that is checked if initializer of static variable inside function was already called.

Pavel Chikulaev
  • 841
  • 6
  • 12
  • 7
    Inheritance should be the tool of last resort, not the first. –  Apr 14 '10 at 10:44
  • A compiler that supports RVO eliminates redundant copying with the function versions. C++0x move semantics eliminate the rest, once they're available. In any case, I doubt it's close to being a bottleneck. –  Apr 14 '10 at 10:56
  • Roger, I'm well aware of RVO, && and move semantics. This is a solution for now in minimal amount of code and entities. Plus all C++0x features won't help with static object inside function example as we are not allowed to define functions inside of functions. – Pavel Chikulaev Apr 14 '10 at 11:07