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 vector
s, string
s, or map
s 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 initializeCTMap
should match. IfN
is less, you get a compilation error. IfN
is greater, zero-initialized pairs ({ 0, 0 }
) will be silently added topairs
. 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 initializeCTMap
with duplicate keys. - Recursion is not necessary in C++14. We can write a
for
loop in aconstexpr
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 variablepairs
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 constantC
will be initialized with20
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 ofC
, its value (20
) will be substituted where it is used, andC
won't be created in the object file even in debug builds.int Foo () { return ctMap[10]; }
– In debug buildsoperator[]
will be called. In release builds MSVC inlinesoperator[]
toFoo
, 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 areturn 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 forctMap[10]
, butFoo
will return0
. You cannot know, that404
was not in the map. To get the compilation error,Foo
has to beconstexpr
and forced to be evaluated in compile time by e.g. assigning it to aconstexpr
variable or an enumerator, using it in a template argument, as a size of a C array, in astatic_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
Deprecated Throw-List in C++11
Why Is There No 2-Byte Float and Does an Implementation Already Exist
How to Check That the Passed Iterator Is a Random Access Iterator
Fast Divisibility Tests (By 2,3,4,5,.., 16)
Is There a C++ Iterator That Can Iterate Over a File Line by Line
Fastest Way to Convert String to Binary
How to Build Boost 1.64 in 64 Bits
Why Can't Std::Ostream Be Moved
How to Include the String Header
C++ [Windows] Path to the Folder Where the Executable Is Located
Do I Have to Use Atomic<Bool> for "Exit" Bool Variable
Calling a Constructor to Re-Initialize Object