Подтвердить что ты не робот

Заполнение массива использованием Constexpr во время компиляции

Я хотел бы заполнить массив enum, используя constexpr. Содержимое массива следует за определенным шаблоном.

У меня есть перечисление, разделяющее набор символов ASCII в четыре категории.

enum Type {
    Alphabet,
    Number,
    Symbol,
    Other,
};

constexpr Type table[128] = /* blah blah */;

Я хотел бы иметь массив из 128 Type. Они могут быть в структуре. Индекс массива будет соответствовать символам ASCII, и значение будет Type каждого символа.

Поэтому я могу запросить этот массив, чтобы узнать, к какой категории принадлежит символ ASCII. Что-то вроде

char c = RandomFunction();
if (table[c] == Alphabet) 
    DoSomething();

Я хотел бы знать, возможно ли это без каких-либо длительных макросов.

В настоящее время я инициализирую таблицу, выполнив следующее.

constexpr bool IsAlphabet (char c) {
    return ((c >= 0x41 && c <= 0x5A) ||
            (c >= 0x61 && c <= 0x7A));
}

constexpr bool IsNumber (char c) { /* blah blah */ }

constexpr bool IsSymbol (char c) { /* blah blah */ }

constexpr Type whichCategory (char c) { /* blah blah */ }

constexpr Type table[128] = { INITIALIZE };

где INITIALIZE - точка входа некоторых очень длинных макросов. Что-то вроде

#define INITIALIZE INIT(0)
#define INIT(N) INIT_##N
#define INIT_0 whichCategory(0), INIT_1
#define INIT_1 whichCategory(1), INIT_2
//...
#define INIT_127 whichCategory(127)

Мне нужен способ заполнить этот массив или структуру, содержащую массив, без необходимости этого макроса hack...

Может быть что-то вроде

struct Table {
    Type _[128];
};

constexpr Table table = MagicFunction();

Итак, вопрос в том, как написать это MagicFunction?

Примечание: Я знаю cctype и любит, этот вопрос скорее Is this possible?, чем Is this the best way to do it?.

Любая помощь будет оценена.

Спасибо,

4b9b3361

Ответ 1

Игнорируя ВСЕ проблемы, indices на помощь:

template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<unsigned... Is>
constexpr Table MagicFunction(seq<Is...>){
  return {{ whichCategory(Is)... }};
}

constexpr Table MagicFunction(){
  return MagicFunction(gen_seq<128>{});
}

Пример в реальном времени.

Ответ 2

В С++ 17 ::std::array был обновлен, чтобы быть более дружественным constexpr, и вы можете делать то же самое, что и в С++ 14, но без некоторых страшных выглядящих хаков, чтобы обойти отсутствие constexpr в критических местах. Вот как выглядит код:

#include <array>

enum Type {
    Alphabet,
    Number,
    Symbol,
    Other,
};

constexpr ::std::array<Type, 128> MagicFunction()
{
   using result_t = ::std::array<Type, 128>;
   result_t result = {Other};
   result[65] = Alphabet;
   //....
   return result;
}

const ::std::array<Type, 128> table = MagicFunction();

Снова MagicFunction по-прежнему нужно соблюдать довольно свободные правила constexpr. В основном, он не может изменять глобальные переменные или использовать new (что подразумевает изменение глобального состояния, а именно кучи) или других подобных вещей.

Ответ 3

IMHO лучший способ сделать это - просто написать крошечную программу установки, которая будет генерировать table для вас. А затем вы можете либо выбросить программу установки, либо проверить ее вместе с исходным кодом.

Трудная часть этого вопроса - всего лишь дубликат этого другого: Возможно ли создать и инициализировать массив значений с помощью метапрограммирования шаблонов?

Фокус в том, что невозможно написать что-нибудь вроде

Type table[256] = some_expression();

в области файлов, так как глобальные массивы могут быть инициализированы только с литеральными списками литералов (исходного уровня). Вы не можете инициализировать глобальный массив с результатом функции constexpr, даже если вы каким-то образом можете вернуть эту функцию для возврата std::initializer_list, чего вы не можете, потому что его конструктор не объявлен constexpr.

Итак, вам нужно заставить компилятор генерировать массив для вас, сделав его членом данных static const класса шаблона. После одного или двух уровней метапрограммирования, которые я слишком смущен, чтобы выписать их, вы окажетесь в строке, которая выглядит примерно как

template <int... Indices>
Type DummyStruct<Indices...>::table[] = { whichCategory(Indices)... };

где Indices - пакет параметров, который выглядит как 0,1,2,... 254,255. Вы создаете этот пакет параметров с использованием рекурсивного вспомогательного шаблона или, возможно, просто используете что-то из Boost. И тогда вы можете написать

constexpr Type (&table)[] = IndexHelperTemplate<256>::table;

... Но зачем вам все это делать, когда таблица содержит всего 256 записей, которые никогда не изменятся, если сам ASCII не изменится? Правильный путь - это самый простой способ: прекомпретировать все 256 записей и явно выписать таблицу без шаблонов, constexpr или любой другой магии.

Ответ 4

Способ сделать это в С++ 14 выглядит так:

#include <array>

enum Type {
    Alphabet,
    Number,
    Symbol,
    Other,
};

constexpr ::std::array<Type, 128> MagicFunction()
{
   using result_t = ::std::array<Type, 128>;
   result_t result = {Other};
   const result_t &fake_const_result = result;
   const_cast<result_t::reference>(fake_const_result[65]) = Alphabet;
   //....
   return result;
}

const ::std::array<Type, 128> table = MagicFunction();

Никаких умных хакеров не требуется. Хотя, поскольку С++ 14 не очень тщательно просматривал то, что было и не должно быть constexpr в стандартной библиотеке, должен использоваться ужасный взлом с участием const_cast.

И, конечно, MagicFunction лучше не изменять глобальные переменные или иначе нарушать правила constexpr. Но эти правила сегодня довольно либеральны. Вы можете, например, изменить все локальные переменные, которые вы хотите, хотя их передача по ссылке или их адреса могут не работать так хорошо.

См. мой другой ответ для С++ 17, который позволяет вам отбросить некоторые уродливые хаки.