I am currently working on implementing a map container in C++, which should be able to function as a compile-time constant. More specifically, my intention is to create a static, pre-defined lookup table. In this table, a key-value pair should evaluate to its corresponding value, during the compilation. If a key is not present, a compile-time error should be thrown.
To illustrate, I am aiming to create a map similar to the following:
map m{
{"pi", 3.14},
{"e", 2.71828}
}
I want m["pi"]
to evaluate to 3.14 during compilation, while m["gamma"]
should produce a compile-time error. The aim here is not only to avoid the necessity of runtime computations, but also to make the code more clear by communicating that the map is a predefined static lookup table, and not something that will change dynamically during runtime.
After a day of hacking, I got the following sniplet:
#include <array>
#include <iostream>
#include <algorithm>
template<typename Key, typename Value, size_t Size>
struct map {
using MapType = std::pair<Key, Value>;
std::array<MapType, Size> data;
constexpr map(std::initializer_list<MapType> init) : data{} {
std::copy(init.begin(), init.end(), data.begin());
}
constexpr Value operator[](const Key& key) const {
auto it = std::find_if(data.begin(), data.end(), [&](const MapType& pair) {
return pair.first == key;
});
if (it == data.end())
throw std::out_of_range("Key not found in map");
return it->second;
}
};
int main() {
constexpr map<int, int, 1> m1 {
{1, 2}
};
std::cout << m1[1] << std::endl;
constexpr map<const char*, float, 2> m2 {
{"pi", 3.14f},
{"e", 2.71828f}
};
std::cout << m2["pi"] << std::endl;
// The line below will raise a runtime error "Key not found in map"
std::cout << m2["gamma"] << std::endl;
}
While the above implementation is operational to a certain extent, it has a couple of issues:
The size needs to be specified in the template which, ideally, I would prefer to avoid.
The map throws a runtime error for non-existent keys like m2["gamma"], when my objective is to enforce this as a compile-time error.
Any suggestions or pointers to enhance the implementation in the context of these issues would be highly appreciated. Thanks!
Addendum:
Per suggestion from @康桓瑋, operator[] can be declared as consteval
, like this:
consteval Value operator[](const Key& key) const {
for (auto i: data) {
if (std::get<0>(i)==key) {
return std::get<1>(i);
}
}
}
This correctly fails to compile when a non-existent key is used, although the error message does not provide relevant information. compiles on C++20+