How to initialize a private static const map in C++?
Asked Answered
N

11

133

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();

};
Neurotic answered 14/4, 2010 at 9:38 Comment(2)
What are the issues you refer to? Are you trying to use this map from another global static variable/constant?Altercate
That's not an associative array string => int, you're mapping an int to a char. v = k + 'a' - 1.Lamb
C
121
#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() {
}
Chemush answered 14/4, 2010 at 9:44 Comment(6)
+1 for simplicity, of course using a Boost.Assign like design is pretty neat too :)Reyreyes
+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).Eureka
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.Plebe
Error is: const_map.cpp:22:23: error: passing ‘const std::map<int, int>’ 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<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = int; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’ discards qualifiers [-fpermissive]Plebe
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. (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).Harkins
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?Asphaltite
P
142

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

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

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

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

Parament answered 31/12, 2011 at 14:21 Comment(6)
Do we need the equal sign at the cpp file at all?Tumult
@phoad: The equal sign is superfluous.Vitale
Thank you for showing the usage. It was really helpful to understand how to modify the static variables.Alarm
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 keysCrooked
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 [ ].Corsica
@Corsica I just added the const keyword, that's a trivial operation; nothing else has to be changed for that.Hadsall
C
121
#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() {
}
Chemush answered 14/4, 2010 at 9:44 Comment(6)
+1 for simplicity, of course using a Boost.Assign like design is pretty neat too :)Reyreyes
+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).Eureka
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.Plebe
Error is: const_map.cpp:22:23: error: passing ‘const std::map<int, int>’ 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<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = int; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’ discards qualifiers [-fpermissive]Plebe
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. (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).Harkins
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?Asphaltite
C
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();
Chee answered 14/4, 2010 at 10:21 Comment(0)
D
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);
Domination answered 14/8, 2013 at 14:40 Comment(0)
A
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
    }
}
Allot answered 14/4, 2010 at 10:33 Comment(3)
+1 for pointing out that a map is not needed, however, you can't iterate over thisBarham
That switch is awful, though. Why not return key + 'a' - 1?Lamb
@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.Allot
E
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")

Emilyemina answered 14/4, 2010 at 10:32 Comment(1)
RVO eliminates the copying in mine and Neil's answer.Chee
M
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.

Milford answered 17/3, 2015 at 15:38 Comment(0)
A
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;
}();
Amphibious answered 21/7, 2013 at 8:23 Comment(0)
C
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
Conlin answered 3/10, 2021 at 14:0 Comment(0)
R
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
}
Roswald answered 14/4, 2010 at 9:51 Comment(11)
A function can certainly be used to initialise a const object.Chemush
In OP's code static map<int,int> myMap = create_map(); is incorrect.Roswald
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.Skerry
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?True
@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]Allot
VS6 hates int type being initialized in such declaration.True
@Basilevs: C and C++ have different initialization rules; was that the issue you remember with gcc? VC6 was prestandard is horribly outdated now.Chee
@Basilevs: Standard C++ hates VS6 and VS6 hates Standard C++... not that much you can do there.Skerry
@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 concernRoswald
@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.Skerry
@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.Allot
C
-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.

Couthie answered 14/4, 2010 at 10:41 Comment(3)
Inheritance should be the tool of last resort, not the first.Chemush
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.Chee
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.Couthie

© 2022 - 2024 — McMap. All rights reserved.