简体   繁体   中英

C++, can I statically initialize a std::map at compile time?

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.

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.

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

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.

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;

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.

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.

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!

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 ;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM