Не пишите шаблонную метапрограмму там, где это не нужно. Попробуйте это простое решение (CTMap
обозначает карту времени компиляции):
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];
Вы получите ошибку компиляции, если вы раскомментируете последнюю строку ( live demo ). Компилятор направит вас к строке KeyNotFound () :
, из которой должна быть очевидна причина сбоя.
Замечания
- Переменная-член
pairs
сделана опубликованной c, чтобы сделать возможным инициализацию карты с помощью инициализации списка. - Заданные
N
и количество пар, которые инициализируют CTMap
, должны совпадать. Если N
меньше, вы получите ошибку компиляции. Если N
больше, инициализированные нулями пары ({ 0, 0 }
) будут тихо добавлены к pairs
. Обратите внимание на это. - Конструктор (сгенерированный компилятором) не проверяет дубликаты ключей.
operator[]
найдет первое, но предполагается, что вы не инициализируете CTMap
с дублирующимися ключами. - Рекурсия не требуется в C ++ 14. Мы можем написать
for
l oop в функции constexpr
( live demo ). Связанная реализация дает еще одну идею для сообщения об ошибке компилятора в случае, если ключ не найден: выдается исключение. Переменная-член pairs
становится закрытой.
Предназначена для использования во время компиляции
Это линейная карта, и параметры передаются по значению. Я предполагал, что карта будет использоваться в коде, оцениваемом во время компиляции, где это не должно быть проблемой.
Обратите внимание также, что при оценке во время выполнения этот класс не даст никакой обратной связи, если ключ не найден на карте.
Давайте подробнее рассмотрим, как ctMap[10]
работает в различных ситуациях. Я пробовал следующее с тремя компиляторами (MSV C v19.24, clang 10.0.0, g cc 9.3).
constexpr int C = ctMap[10];
- Глобальная константа C
будет инициализируется 20
даже в отладочных сборках. Во время выполнения вычисление не производится. Обратите внимание, что для обеспечения того, что глобальный будет создан, вы должны где-то взять его адрес. Если вы используете значение C
, его значение (20
) будет заменено там, где оно используется, и C
не будет создано в объектном файле даже в отладочных сборках. int Foo () { return ctMap[10]; }
- В отладочных сборках будет вызываться operator[]
. В выпусках сборки MSV C встраивает от operator[]
до Foo
, т.е. исключает один вызов, но результирующий код имеет линейную сложность (компилятор не обязан выполнять вычисления во время компиляции, а оптимизация кода в MSV плохая) 1085 *). Clang и g cc компилируют return 20;
.
И вот как ctMap[404]
работает (с теми же тремя компиляторами):
constexpr int C = ctMap[404];
- Не компилируется, как указано выше. int Foo () { return ctMap[404]; }
- применяются те же замечания, что и для ctMap[10]
, но Foo
вернет 0
. Вы не можете знать, что 404
не было на карте. Чтобы получить ошибку компиляции, Foo
должен быть constexpr
и принудительно оцениваться во время компиляции, например, присваивая его переменной constexpr
или перечислителю, используя его в качестве аргумента шаблона, в качестве размера * Массив 1087 *, в static_assert
, et c.