views:

734

answers:

6

Hi, I need just dictionary or asociative array string => int.

There is type map C++ for this case. But I need make one map in my class make for all instances(-> static) and this map cannot 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();

};

thanks

+13  A: 
#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() {
}
anon
+1 for simplicity, of course using a `Boost.Assign` like design is pretty neat too :)
Matthieu M.
+2  A: 

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
A function can certainly be used to initialise a const object.
anon
In OP's code `static map<int,int> myMap = create_map();` is incorrect.
Prasoon Saurav
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
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
@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
VS6 hates int type being initialized in such declaration.
Basilevs
@Basilevs: C and C++ have different initialization rules; was that the issue you remember with gcc? VC6 was prestandard is horribly outdated now.
Roger Pate
@Basilevs: Standard C++ hates VS6 and VS6 hates Standard C++... not that much you can do there.
David Rodríguez - dribeas
@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
@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
@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
+6  A: 

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();
Roger Pate
+2  A: 

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
RVO eliminates the copying in mine and Neil's answer.
Roger Pate
+1  A: 

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
+1 for pointing out that a map is not needed, however, you can't iterate over this
Viktor Sehr
That `switch` is awful, though. Why not `return key + 'a' - 1`?
Johnsyweb
@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
A: 

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
Inheritance should be the tool of last resort, not the first.
anon
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.
Roger Pate
Pavel Chikulaev