Создание карты Key-Value во время компиляции в C ++ - PullRequest
2 голосов
/ 18 апреля 2020

Я пытался создать карту времени компиляции , простую , ключ-значение в C ++. Я компилирую с /std:c++11. (Использование компилятора IAR для встроенного кода и на данный момент поддерживается только cpp ++ 11)

Я немного узнал о метапрограммировании.

Я не хочу, чтобы моя карта имела значение по умолчанию, если ключ не найден , как этот пост: Как создать хранилище ключей / значений во время компиляции?

Я хочу получить ошибку компилятора, если в своем коде я пытаюсь получить значение, которое не хранится на карте.

Вот что я сделал:


#include <iostream>




template <int kk, int vv>
struct KeyValue
{
    static const int k = kk, v = vv;
};


// Declaration 
template <typename kv, typename...>
struct CompileTimeMap;


// Recursive Definition 
template<typename kv, typename... rest>
struct CompileTimeMap<kv, rest...>
{
    template<int k_input>
    struct get
    {
        static const int val = (k_input == kv::k) ? kv::v : CompileTimeMap<rest...>::get<k_input>::val;
    };
};


// Base Definition 
template <typename kv>
struct CompileTimeMap<kv>
{
    template<int k_input>
    struct get
    {
        static const int val = (k_input == kv::k) ? kv::v;
    };
};




// ----------------------------- Main  -----------------------------

typedef CompileTimeMap<KeyValue<10, 20>, KeyValue<11, 21>, KeyValue<23, 7>> mymap;

int main()
{
    // This calles should be ok !! :) 
    std::cout << mymap::get<10>::val << std::endl;
    std::cout << mymap::get<11>::val << std::endl;
    std::cout << mymap::get<23>::val << std::endl;


    // This line should resolve a compile error !! (there is no key of 33) 
    std::cout << mymap::get<33>::val << std::endl;
}

Я получаю следующую ошибку: error C2131: expression did not evaluate to a constant.

Как я могу сделать эту работу? Большое спасибо:)

1 Ответ

2 голосов
/ 18 апреля 2020

Не пишите шаблонную метапрограмму там, где это не нужно. Попробуйте это простое решение (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.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...