41

If I code this

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

then g++ says to me

deducing from brace-enclosed initializer list requires #include <initializer_list>
in C++98 ‘example’ must be initialized by constructor, not by ‘{...}’   

and that annoys me slightly because the constructor is run-time and can, theoretically fail.

Sure, if it does, it will fail quickly and ought to do so consistently, so that I ought to quickly locate & correct the problem.

But, still, I am curious - is there anyway to initialize map, vector, etc, at compile time?


Edit: I should have said that I am developing for embedded systems. Not all processors will have a C++0x compiler. The most popular probably will, but I don't want to encounter a gotcha & have to maintain 2 versions of the code.

As to Boost, I am undecided. They are wishy-washy on the use of their Finite State Machine classes in embedded systems, so that is actually what I am coding here, Event/State/Fsm classes.

Sigh, I guess I'd better just play it safe, but I hope that this discussion has been helpful for others.

Mawg says reinstate Monica
  • 36,598
  • 98
  • 292
  • 531

8 Answers8

38

It's not exactly static initialization, but still, give it a try. If your compiler doesn't support C++0x, I'd go for std::map's iteration constructor:

std::pair<int, std::string> map_data[] = {
    std::make_pair(1, "a"),
    std::make_pair(2, "b"),
    std::make_pair(3, "c")
};

std::map<int, std::string> my_map(map_data,
    map_data + sizeof map_data / sizeof map_data[0]);

This is pretty readable, doesn't require any extra libraries and should work in all compilers.

Dmitry
  • 6,450
  • 2
  • 24
  • 19
  • 8
    Note that this approach will construct allocate memory to hold each of the temporary std::pair items in your map_data, copy the statically declared strings into pair::second, then copy the text again into the std::map, and finally destruct the temporaries. For a large number of statically declared items, this would be quite wasteful. – Armentage Jun 08 '12 at 12:40
22

Not in C++98. C++11 supports this, so if you enable C++11 flags and include what g++ suggests, you can.

Edit: from gcc 5 C++11 is on by default

doron
  • 26,460
  • 11
  • 62
  • 99
Artyom
  • 29,986
  • 21
  • 123
  • 211
  • 1
    Yes this has been a missing feature for very long. I really hope C++0x turns out to be C++OA so we can start demanding this from our compiler vendors. – daramarak Jan 31 '10 at 14:48
  • 1
    Hm, if i read it right, implementations *may* statically initialize non-aggregates too if it the semantics are not changed. If the static analysis can't guarantee that, C++0x initializer lists wouldn't change a thing. – Georg Fritzsche Jan 31 '10 at 15:54
  • @daramarak: I think the compiler vendors are the ones writing the standard ;v) , support should follow soon after the spec in major compilers whether you ask for it or not, and for minor compilers, good luck. – Potatoswatter Feb 01 '10 at 02:57
  • @daramarak C++0B, specifically C++0B-03 (`__cplusplus` is defined as 201103L) :P – moshbear Nov 05 '11 at 11:13
  • I tried by enabling c++0x support but I am getting error. g++ main.cc -std=c++0x main.cc:14:38: error: could not convert ‘{(0, 'a'), (0, 'b'), (0, 'c')}’ from ‘’ to ‘std::map – Vivek Goel Jun 30 '12 at 14:07
  • 5
    I think syntax should be like this { {1, 'a'} }; – Vivek Goel Jun 30 '12 at 14:09
  • VS 2012 doesn't support it. – SmallChess Jul 11 '14 at 08:25
  • @StudentT - I'm not sure about VS 2012 but it does on VS 2015. The syntax is wrong in the example, see Vivek Goel's comment above yours. – Class Skeleton Jul 17 '15 at 10:30
  • 1
    I would bet money that no compiler as of today will compile this into static initialization. Not for `std::map`. Maybe with C++17 it will be possible to implement a `constexpr` map/hashtable -- but probably not with the `std::map` interface. – Paul Groke Jul 28 '15 at 13:44
  • "C++11 supports this"? How? – ThomasMcLeod Nov 11 '17 at 13:44
14

You can use Boost.Assign library:

#include <boost/assign.hpp>
#include <map>
int main()
{
   std::map<int, char> example = 
      boost::assign::map_list_of(1, 'a') (2, 'b') (3, 'c');
}

However, as Neil and others pointed in the comments below, this initialization occurs in runtime, similarly to UncleBean's proposal.

mloskot
  • 35,141
  • 11
  • 102
  • 125
14

With C++0x you might need to use braces all the way (use the new-style syntax for each pair as well):

std::map<int, char> example = { {1,'a'}, {2, 'b'}, {3, 'c'} };

Those brackets to construct pairs are not meaningful. Alternatively you can fully name out each pair or use make_pair (as you'd do in C++98)

std::map<int, char> example = {
    std::make_pair(1,'a'),
    std::make_pair(2, 'b'),
    std::make_pair(3, 'c')
};

As to creating those instances at compile-time: no. STL containers all encapsulate entirely runtime memory management.

I suppose, you'd only really have a compile-time map with libraries like boost's metaprogramming (not 100% sure, if it is entirely correct, and haven't studied what it could be good for):

using namespace boost::mpl;
map<
    pair<integral_c<int, 1>, integral_c<char, 'a'> >,
    pair<integral_c<int, 2>, integral_c<char, 'b'> >,
    pair<integral_c<int, 3>, integral_c<char, 'c'> >
> compile_time_map;
UncleBens
  • 39,805
  • 6
  • 53
  • 90
  • UncleBens, your second example doesn't compile, this approach IMO _has_ to be done like I posted in my answer. – Dmitry Jan 31 '10 at 16:07
  • @Dmitry: The second example assumes -std=C++0x as well (OP's compiler appears to support it, or it wouldn't be talking about initializer_list). – UncleBens Jan 31 '10 at 19:32
  • UncleBens, oh, ok, didn't know that, my gcc is a bit old, no C++0x for me yet. – Dmitry Jan 31 '10 at 20:05
4

With pre-C++0x, the closest thing you can get is by not using containers designed for runtime usage (and limiting yourself to fundamental types and aggregates):

struct pair { int first; char second; };
pair arr[] = {{1,'a'}, {2,'b'}}; // assuming arr has static storage

This could then be accessed using some kind of map view, or you could implement a wrapper that allows for aggregate initialization similar to what Boost.Array does.

Of course the question is wether the benefits justify the time put into implementing this.

If my reading is correct here, C++0x initializer-lists may give you static initialization of non-aggregates like std::map and std::pair, but only if that doesn't change semantics compared to dynamic initialization.
Thus, it seems to me you can only get what you asked for if your implementation can verify via static analysis that the behaviour doesn't change if the map is statically initialized, but no guarantees that it happens.

Georg Fritzsche
  • 95,426
  • 26
  • 188
  • 233
1

There is a trick you can use, but only if this data will not be used in any other static constructor. First define a simple class like this:

typedef void (*VoidFunc)();
class Initializer
{
  public:
    Initializer(const VoidFunc& pF)
    {
      pF();
    }
};


Then, use it like this:

std::map<std::string, int> numbers;
void __initNumsFunc()
{
  numbers["one"] = 1;
  numbers["two"] = 2;
  numbers["three"] = 3;
}
Initializer __initNums(&__initNumsFunc);


Of course this is a bit overkill so I would recommend using it only if you really have to.

Adis H
  • 624
  • 4
  • 5
  • 9
    Double underscores are reserved for the compiler. – GManNickG Jan 31 '10 at 16:04
  • 1
    I think that's just another variation of the C++0x and boost.assign suggestions. There's nothing compile-time here, just another way to set values in a global map instance. – UncleBens Jan 31 '10 at 16:05
  • I used the underscores just to make it clear that you wouldn't want to touch the function or the object in the remaining code. (you could use an unnamed namespace too) And yes nothing compile-time about it but as I said, it's a trick - the data will be initialized before main() – Adis H Jan 31 '10 at 16:13
1

There is no standard way to initialize std::map at compile time. As others have mentioned, C++0x will allow the compiler to optimize the initialization to be static if possible, but that will never be guaranteed.

Remember, though, that the STL is just an interface spec. You can create your own compliant containers and give them static initialization capability.

Depending on whether you plan on upgrading your compiler and STL implementation (particularly on an embedded platform), you might even just dig into the implementation you're using, add derived classes, and use those!

Potatoswatter
  • 131,100
  • 23
  • 249
  • 407
  • It's impossible for any implementation of `std::map` to be compliant and support static initialization. `swap()` iterator validity requirements make it absolutely necessary to use dynamic storage, for `std::map` and all other standard containers except `std::array`. – Ben Voigt Jul 25 '17 at 22:48
  • @BenVoigt This answer does appear to be based on a misconception of the connection between braces and static-ness. However, it is possible for a compiler to put `const map>>` nodes in ROM, as long as `std::allocator` isn't specialized. If the compiler can detect that node deallocation amounts to a trivial destructor and a `free`, and it knows that `free` ignores ROM addresses, then it can skip `malloc` and allocate ROM statically. That said, certainly no compiler has ever done so. (This Q is less `const`, but the principle is the same.) – Potatoswatter Jul 31 '17 at 20:31
-2
template <const int N> struct Map  { enum { value = N}; };
template <> struct Map <1> { enum { value = (int)'a'}; };
template <> struct Map <2> { enum { value = (int)'b'}; };
template <> struct Map <3> { enum { value = (int)'c'}; };

std::cout  << Map<1>::value ;