C++, How to Statically Initialize a Std::Map at Compile Time

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

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

Initialize static const std::map during compile time?

No, you can't initialize the std::map with data in compile time!

However, you can use this "fancier" initializer if you prefer, then you can have your data in a const std::map, in case this is what you are trying to do.

static const map<string, int> m = {
{ "a", 1 },
{ "b", 2 }
};

But AGAIN, this WILL NOT initialize the std::map itself in compile time. Behind the scenes, std::map will do the job in runtime.

In C++, how do I populate a map at compile-time, given a vector of strings?

There are no vectors, strings, or maps at compile time. In fact, an object of class type with a non-trivial destructor can't be used in a constexpr context. So this simply can't be done.

From c++20, you can have non-trivial destructors, so this is possible in principle. As far as I know, only vector and string are available in constexpr contexts though, and even then, all the storage that they allocate, must be deallocated before run-time. So you can't even have a vector or string at compile time that you can also use at run-time.

From c++20, you can guarantee that the vector or string, or map is initialized at program-load time though, using the keyword constinit.

In case you just want to do a complex initialization of a const map, you can do that with an IIILE (immediately invoked initializing lambda expression) even before c++20. e.g.

const map<string, int> m = [] {
map<string,int> x; // construct a map ...
x["hi"] = 42; // populate it
return x;// return it
}(); // and call the expression straight away.

C++14 Static class map initialization

You need to "define" your map after you "declared" it:
See: https://en.cppreference.com/w/cpp/language/static

#include <map>
#include <string>

struct SomeInfo
{
std::string id;
std::string name;
};

enum MyEnum {
Enum1, Enum2

};

class Foo
{
private:
static const std::map<MyEnum, SomeInfo> fooMap;
public:
static std::map<MyEnum, SomeInfo> getMap()
{
return fooMap;
}
};

const std::map<MyEnum, SomeInfo> Foo::fooMap = {
{MyEnum::Enum1, SomeInfo{ "info1", "Info 1" }},
{MyEnum::Enum2, SomeInfo{ "info2", "Info 2" }}
};

int main(){
auto val = Foo::getMap()[MyEnum::Enum1];
return 0;
}

And if you want to make your type not constructable you can delete the compiler generated default constructor via Foo() = delete; - it must not be private.

Creating compile-time Key-Value map in C++

Don't write a template metaprogram, where it is not necessary. Try this simple solution (CTMap stands for compile time map):

template <class Key, class Value, int N>
class CTMap {
public:
struct KV {
Key key;
Value value;
};

constexpr Value operator[] (Key key) const
{
return Get (key);
}

private:
constexpr Value Get (Key key, int i = 0) const
{
return i == N ?
KeyNotFound () :
pairs[i].key == key ? pairs[i].value : Get (key, i + 1);
}

static Value KeyNotFound () // not constexpr
{
return {};
}

public:
KV pairs[N];
};

constexpr CTMap<int, int, 3> ctMap {{ { 10, 20 }, { 11, 21 }, { 23, 7 } }};

static_assert (ctMap[10] == 20, "Error.");
static_assert (ctMap[11] == 21, "Error.");
static_assert (ctMap[23] == 7, "Error.");

// constexpr auto compilationError = ctMap[404];

You will get a compilation error, if you uncomment the last line (live demo). The compiler will direct you to the KeyNotFound () : line, from
which the reason of the failure should be obvious.

Remarks

  • The member variable pairs is made public, to make it possible to initialize the map with list-initialization.
  • The given N and the number of pairs that initialize CTMap should match. If N is less, you get a compilation error. If N is greater, zero-initialized pairs ({ 0, 0 }) will be silently added to pairs. Pay attention to this.
  • The (compiler generated) constructor does not check for duplicate keys. operator[] will find the first, but the intended usage is that you do not initialize CTMap with duplicate keys.
  • Recursion is not necessary in C++14. We can write a for loop in a constexpr function (live demo). The linked implementation gives another idea for giving a compiler error in case the key is not found: an exception is thrown. The member variable pairs is made private.

Intended to be used in compile time

This is a linear map, and parameters are passed by value. My intention was that the map will be used in compile time evaluated code, where this should not be a problem.

Note also that when evaluated in run time, this class won't give any feedback if the key is not found in the map.

Let's take a closer look of how ctMap[10] works in different situations. I have tried the following with three compilers (MSVC v19.24, clang 10.0.0, gcc 9.3).

  • constexpr int C = ctMap[10]; – The global constant C will be initialized with 20 even in debug builds. No computation is made during run-time. Note that to ensure, that the global will be created, you have to take its address somewhere. If you use the value of C, its value (20) will be substituted where it is used, and C won't be created in the object file even in debug builds.
  • int Foo () { return ctMap[10]; } – In debug builds operator[] will be called. In release builds MSVC inlines operator[] to Foo, i.e. eliminates one call, but the resulting code has linear complexity (the compiler is not forced to do the computation in compile time, and code optimization is poor in MSVC). Clang and gcc compiles a return 20;.

And this is how ctMap[404] works (with the same three compilers):

  • constexpr int C = ctMap[404]; – Does not compile, as mentioned above.
  • int Foo () { return ctMap[404]; } – The same remarks apply as for ctMap[10], but Foo will return 0. You cannot know, that 404 was not in the map. To get the compilation error, Foo has to be constexpr and forced to be evaluated in compile time by e.g. assigning it to a constexpr variable or an enumerator, using it in a template argument, as a size of a C array, in a static_assert, etc.

Checking a map statically at compile time?

Store the map as a type.

Use the type to build your runtime typeid map.

Here is the key-value pair for your compile-time map:

template<class Key, class Value>
struct entry {
using key=Key;
using value=Value;
};

We then make a map from it:

template<class T> struct tag_t{using type=T; constexpr tag_t(){};};
template<class T> constexpr tag_t<T> tag{};

template<class...Entries>
struct compile_time_map:Entries... {
template<class Key>
struct lookup {
template<class Value>
constexpr tag_t<Value> operator()( entry<Key, Value> const& ) const { return {}; }
};
template<class Key>
constexpr
std::result_of_t< lookup<Key>( compile_time_map ) > operator()( tag_t<Key> ) const {
return {};
}
template<class...Key>
constexpr std::false_type operator()( tag_t<Key...> ) const {
return {};
}
template<class MessageID>
std::map< std::type_index, MessageID > make_map() const {
return { {typeid(typename Entries::key), typename Entries::value{}}... };
}

template<class Key>
constexpr auto has(tag_t<Key> ={}) const {
return std::integral_constant<bool, !std::is_same< std::result_of_t< compile_time_map(tag_t<Key>)>, std::false_type >{}>{};
}
};

This map can generate a run-time map of a type of your choice.

using ID=unsigned;

template<class T, ID id>
using make_entry = entry<T, std::integral_constant<ID, id>>;

using ctm = compile_time_map<
make_entry< int, 7 >,
make_entry< double, 3 >,
make_entry< std::string, (unsigned)-1 >
>;

auto rtm = ctm{}.make_map<ID>();

we can do compile-time lookups with ctm{}( tag<int> ). We can do runtime lookups with rtm[ typeid(int) ].

We can check if there is an entry at compile time with ctm{}.has<int>() or ctm{}.has( tag<int> ).

live example.

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

#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() {
}

Elegant way to ensure a std::map has a concrete size in compilation time

You can store the data in a datatype that can be inspected at compile time, such as an array.

static const std::map<EventTypes, std::string>::value_type kEventTypesNamesData[] = {
// Note "value_type", here ^^^^^^^^^^
{ EventTypes::InitSuccessfull, "InitSuccessfull" },
{ EventTypes::KeyPressed, "KeyPressed" },
{ EventTypes::StartedCleanup, "StartedCleanup" },
{ EventTypes::FinishedCleanup, "FinishedCleanup" },
{ EventTypes::Exit, "Exit" }
};

// Compile-time size check
static_assert(end(kEventTypesNamesData)-begin(kEventTypesNamesData) == static_cast<std::underlying_type<EventTypes>::type>(EventTypes::count));

// Construct from data
static const std::map<EventTypes, std::string> kEventTypesNames( begin(kEventTypesNamesData), end(kEventTypesNamesData) );

Initializing a static map using static members

I cannot reproduce this problem with GCC 6.1.0. However, it can be reproduced any time you try to bind a reference to a constexpr variable you haven't defined, which is probably what your std::map constructor does :

struct Foo {
static constexpr int i = 42;
};

int main() {
auto const &p = Foo::i; // undefined reference to `Foo::i'
}

This is because binding the reference is an ODR-use of i, which requires a unique definition to exist at link-time.

There is a simple workaround that works in most such cases, and that is to apply a unary +:

struct Foo {
static constexpr int i = 42;
};

int main() {
auto const &p = +Foo::i; // OK!
}

Applying + does not ODR-use i, since only its value is needed, not its identity. Then the reference binds to the temporary returned by +, instead of i.



Related Topics



Leave a reply



Submit