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
  • 38,334
  • 103
  • 306
  • 551
  • 1
    Check out http://www.state-machine.com/ for an embedded SM library. – rpg Feb 01 '10 at 09:17
  • Thanks, I know of it (but still +1 as it may help others). It seems a little too much for me, but then I do also need o/s abstraction, so ... maybe ... Perhaps it's just Not-Invented-Here syndrome ;-) – Mawg says reinstate Monica Feb 01 '10 at 13:12
  • 1
    Here is the [answer to a similar stack overflow question](http://stackoverflow.com/a/1730798/758666), that makes a clever use of a template class and operator overloading. – wip Jul 13 '12 at 05:09
  • Wow, gcc really worked on their error messages. – Tomáš Zato Jan 17 '17 at 14:13
  • 1
    In c++11 (clang) I get an error for the above syntax, replacing the parenthesis with curly brackets fixed it. – Jehandad Jul 03 '18 at 16:32

9 Answers9

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,590
  • 2
  • 26
  • 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
  • 27,972
  • 12
  • 65
  • 103
Artyom
  • 31,019
  • 21
  • 127
  • 215
  • 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. – ABCD 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
  • 37,086
  • 11
  • 109
  • 136
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
  • 40,819
  • 6
  • 57
  • 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
  • 97,545
  • 26
  • 194
  • 236
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
  • 134,909
  • 25
  • 265
  • 421
  • 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
0

No, because, all of the components of map and vector and strings, in your example, are all constructed with memory that is allocated at runtime via new. The use of std::initializer_list and other sugar ultimately is just doing the constructor work we are used to on our behalf.

To make it be truly compile time, as in, your structure in obj, you'd have to compile the program partially, run it, serialize the results of the initialier to the obj, and then, revert the process on load. Doing this with simple arrays and strings and scalars isn't too bad, but a whole complex set of structures is another matter.

But hold that serialization thought. What you could do is, do something like us nlohmann json to serialize / deserialize your structure, and then, have the string json of your data structure as a string constant. That data would be quite happy at compile time, and then you could load from that at runtime. Or, you could just use what you have, and cobble together a collection of known compile time constructs and read from those at runtime. For example, you could represent your map as a 2xN array, and then read that array at runtime.

TJ Bandrowsky
  • 842
  • 8
  • 12
-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 ;