Сильно перечислить в целое и наоборот

Вот мой источник (как ответ), как реализовать функции basic_value и to_enum.
Как автоматически конвертировать строго типизированный enum в int?

лежащее в основе — нет проблем.
но, to_enum — есть проблема.

увидеть:

enum class E{ a = 1, b = 3, c = 5 };
auto e_a = utils::underlying_value(E::a); //OK
E t = utils::to_enum<E>( 2 ) ; // compiled, but it's incorrect. I think here must throws exception?

Q: как правильно реализовать to_enum?

1

Решение

Несмотря на комментарии по этому вопросу, это может быть сделано в C ++ 11, хотя, чтобы сделать это без повторения кода, вам в конечном итоге придется обернуть enum class объявление в макросе. Это может сделать мой ответ неподходящим, в зависимости от ваших потребностей. В любом случае для выполнения проверенного преобразования требуется некоторое оборудование, поэтому я вернусь к макросу последним.

Основная идея заключается в использовании constexpr Функции для сканирования массива:

#include <iostream>
#include <stdexcept>

enum class E { a = 1, b = 3, c = 5 };

constexpr E         values[] = {E::a, E::b, E::c};
constexpr size_t    count = sizeof(values) / sizeof(E);

constexpr E to_enum(int value, size_t index = 0)
{
return
index >= count ? throw std::runtime_error("invalid integer") :
static_cast<int>(values[index]) == value ? values[index] :
to_enum(value, index + 1);
}

constexpr E         converted = to_enum(3);

// Will not compile if uncommented.
// constexpr E         bad_converted = to_enum(2);

int main()
{
std::cout << static_cast<int>(converted) << std::endl;

return 0;
}

Это печатает 3, Если строка с bad_converted без комментариев, этот код вообще не будет компилироваться, как говорится. Проверенное преобразование может быть выполнено во время выполнения или во время компиляции. Это будет сделано во время компиляции, если аргумент to_enum является константой времени компиляции. Кроме того, как вы, вероятно, видите, это делает линейное сканирование values, но это может быть заменено другим алгоритмом, если это становится проблемой производительности для очень большого перечисления.


Код, который я только что показал, представляет собой набросок, показывающий базовый метод. Чтобы сделать это менее трудным, вы должны завернуть декларацию E в макросе, который будет автоматически генерировать values[] массив и связанные с ним функции. Я покажу и объясню содержание этого макроса по одной точке за раз.

Основной макрос выглядит так

// Declaration header
#define ENUM_CLASS(TypeName, __VA_ARGS__)

// Use
ENUM_CLASS(E, a = 1, b = 3, c = 5);

Итак, в этом примере __VA_ARGS__ будут жетоны a = 1, b = 3, c = 5, Итак, мы можем объявить само перечисление внутри макроса следующим образом:

enum class TypeName { __VA_ARGS__ };

Однако мы не можем просто заявить:

constexpr TypeName values[] = { __VA_ARGS__ };

потому что расширяется до

constexpr TypeName values[] = { a = 1, b = 3, c = 5 };

который не ограничен (отсутствует TypeName:: перед каждым значением), и не является допустимым C ++ из-за дополнительных операторов присваивания внутри инициализатора массива. Сначала я решу вторую проблему. Вы должны определить класс как этот:

template <typename E>
class swallow_assignment {
public:
E _value;

constexpr explicit swallow_assignment(E value) : _value(value)
{
}

template <typename Any>
constexpr const swallow_assignment& operator =(Any other) const
{
return *this;
}

constexpr operator E() const
{
return _value;
}
};

Теперь вы можете написать (swallow_assignment<E>)E::a = 1, Что произойдет, во время компиляции, E::a будет преобразован в присваиваемое значение (swallow_assignment<E>)E::a, который имеет такое же внутреннее представление, как E::a, Это значение будет затем игнорировать присвоение 1, а затем будет преобразован обратно в E::a,


Осталось префикс каждой из объявленных констант, чтобы мы получили

constexpr TypeName values[] =
{(swallow_assignment<E>)E::a = 1,
(swallow_assignment<E>)E::b = 3,
(swallow_assignment<E>)E::c = 5})

который теперь будет действительным инициализатором. Это можно сделать с помощью макроса отображения. Я не буду вдаваться в детали, потому что это отдельная тема, но такой макрос можно найти здесь. https://github.com/aantron/better-enums/blob/e28177b11a9e3d7152c5216d84fdf8939aff0eba/enum_preprocessor_map.h. Повышение также может быть лучше. Какой бы макрос вы ни использовали, я предполагаю, что его подпись PP_MAP(prefix, __VA_ARGS__), Эскиз для окончательного определения макроса для всего перечисления становится:

#define ENUM_CLASS(TypeName, __VA_ARGS__)                  \
enum class TypeName { __VA_ARGS__ };                   \
constexpr TypeName values[] =                          \
{ PP_MAP((swallow_assignment<TypeName>)TypeName::, \
__VA_ARGS__) };                           \
constexpr size_t count = sizeof(values) / sizeof(TypeName);

Возможно, вы захотите включить эти определения в специализацию типа черт, чтобы вы могли использовать этот макрос с более чем одним enum class (в противном случае массивы по имени values будет сталкиваться). Возможно, вам придется использовать слабые символы, чтобы избежать проблем со связью, если вы делаете values статический член класса черт, однако.

Эти последние пункты оставлены в качестве упражнения, потому что этот ответ уже слишком длинный 🙂 У меня есть библиотека, которая выполняет все вышеперечисленное, хотя она оборачивает enum вместо обеспечения специализации черт для enum class, Существует неопубликованная ветка с комбинацией enum class/ черты, однако. Вы можете увидеть библиотеку здесь: http://aantron.github.io/better-enums. Библиотека ::_from_integral() метод соответствует to_enum функция в вашем вопросе, и она выполняет преобразования во время выполнения и во время компиляции.

1

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

Других решений пока нет …

По вопросам рекламы [email protected]