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

Я хотел бы заполнить массив перечислений с помощью 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)

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

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

struct Table {
Type _[128];
};

constexpr Table table = MagicFunction();

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

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

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

Спасибо,

18

Решение

игнорирование ВСЕ Проблемы, индексы в помощь:

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>{});
}

Живой пример.

26

Другие решения

В С ++ 17 ::std::array был обновлен, чтобы быть больше constexpr дружественный и вы можете сделать то же самое, что и в C ++ 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 (что подразумевает изменение глобального состояния, а именно кучи) или другие подобные вещи.

7

ИМХО лучший способ сделать это просто написать крошечную программу установки, которая будет генерировать 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

Способ сделать это в C ++ 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();

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

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

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

3