C++20 и новее – как лучше всего реализовать «перечисление с большей функциональностью»?

За прошедшие годы я видел множество тем о том, как можно обернуть перечисление или создать шаблон класса с помощью перечисления, чтобы добавить дополнительную функциональность.

Рассмотрим вариант использования. У меня есть перечисление. Я хочу иметь возможность создавать объект класса вокруг класса перечисления, чтобы я мог

  • преобразовать в строку
  • конкретная информация о возвращаемом значении

Простой способ сделать это


enum class color : uint16_t {
unknown = 0,
red,
green,
blue,
};

class colorClass {
public:
colorClass(){ val = color::unknown; }
colorClass(color v)
 :val(v)
{}

//copy, move, assignment ctors section

bool isRed(){ return val == color::red; }
// more such functions

std::string toString() {
using enum color;
switch (val) {
  case red : return "red";
  case blue: return "blue";
  case green: return "green";
  case unknown: [[fallthrough]];
  default: return "unknown";
}
}

private:
 color val;
};

Это работает отлично. Теперь, если я хочу еще немного ограничить этот класс, я могу использовать шаблоны и концепции.

enum class color : uint16_t {
unknown = 0
red,
green,
blue,
};


template<typename E>
concept ValidColor = std::is_enum_v<E>;

template<ValidColor E>
class colorClass {
// Pretty much the same implementation?
}; 

Моя память немного размыта, но я думаю, что видел несколько умных реализаций toString с использованием базового типа. Я не собираюсь использовать какие-либо сторонние библиотеки.

У меня есть «современный» путь, верно? Помимо принудительного использования перечисления, дает ли добавление шаблонов какие-либо преимущества?

Обновлено: я спрашиваю не только об размышлениях (и спасибо за все ответы в этом направлении), но и о том, как C++20 и C++23 могут улучшить этот класс "перечисления с помощью нескольких трюков".

Невозможно заставить компилятор сопоставить имена перечислений со строками. Есть несколько библиотек отражений, которые помогут вам в этом. Возможно, C++26 получит отражение, и это можно будет сделать изначально.

NathanOliver 18.06.2024 04:51

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

NathanOliver 18.06.2024 04:52

Вы можете назначать имена перечислений и строки, не повторяя их, используя X-макросы, но это, конечно, не современный способ — он работает даже в чистом C.

Eugene 18.06.2024 05:10

В ваших примерах все методы colorClass являются частными, а метод toString не принимает нулевые аргументы.

paddy 18.06.2024 05:30

Загрузите Magic Enum с github. Задача решена. (Если только ваша проблема не в абсолютной переносимости на любой совместимый компилятор, который только можно вообразить).

n. m. could be an AI 18.06.2024 05:47

Обязательно прочитайте P2996: отражение для C++26, чтобы увидеть, чего можно ожидать в будущем, и убедиться, что то, что вы делаете сейчас, не слишком далеко отходит от интерфейса, который вы предоставите, когда в C++ будет статическое отражение этого. добрый.

Toby Speight 18.06.2024 10:46

@NathanOliver Я исправил код, спасибо за вклад

user14530855 18.06.2024 16:47
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
250
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Есть рабочий трюк для случаев с максимальным значением перечисления 0x8000 или около того (ограничение ICE для std::make_index_sequence). Затем вы можете сопоставить строки со значениями, используя массив, заполненный элементами std::source_location. Затем вам нужно обрезать строки из-за искажения имен, зависящих от платформы:

template<typename enm>
requires std::is_enum_v<enm>
constexpr std::string_view enum_raw_name()
{return std::source_location::current().function_name();};

constexpr std::string_view enum_demangle_trim(std::string_view);//has platform specific implementation 

template<typename enm>
requires std::is_enum_v<enm>
constexpr std::pair enum_bounds{ 
    std::to_underlying(enm{}),
    std::numeric_limits<std::underlying_t<enm>>::max()
};

template<typename enm>
requires std::is_enum_v<enm>
constexpr std::string_view enum_value_name(enm const e, std::underlying_type_t<enm> u = {}) {
     using int_t = decltype(u);
     constexpr auto bounds  = enum_bounds<enm>;
     auto constexpr range = std::min(0x8000uz,std::size_t{bounds.second}-bounds.first);
     static auto constexpr map = []<std::size_t ... i>(std::index_sequence<i ...>){
            return std::array{ 
                   enum_demangle_trim(enum_raw_name<static_cast<enm>(int_t{i} + bounds.first)>())
            ..., "out of range"};
     }(std::make_index_sequence<range>{});
     return map[min(std::size_t{int_t{e}}-bounds.first, range)];
};

Ограничениями вышеприведенной реализации являются максимальное значение типа перечисления и отрицательные значения перечисления, которые считаются выходящими за пределы. Метапрограммирование также может включать отрицательные значения; Шаблон переменной enum_bounds должен быть явно специализированным.

Другая болевая точка — это платформо-зависимая реализация enum_demangle_trim-, которая должна сжимать базовую строку с обеих сторон для получения имен перечислений.

Ответ принят как подходящий

Несмотря на то, что сейчас 2024 год, и я использую C++20, я все еще использую X-макросы для этого, поскольку мне еще предстоит найти лучшее решение (без внешней библиотеки):

#define DEFINE_ENUM_VAL(class_name, name) name,
#define DEFINE_ENUM_STR_CONVERSION(class_name, name) case class_name::name: return #name;

#define FOR_EACH_FOOBAR(DO) \
    DO(FooBar, ONE) \
    DO(FooBar, TWO) \
    DO(FooBar, THREE)

enum class FooBar
{
    FOR_EACH_FOOBAR(DEFINE_ENUM_VAL)
};

inline auto to_string(FooBar val) -> std::string
{
    switch (val) {
        FOR_EACH_FOOBAR(DEFINE_ENUM_STR_CONVERSION)
    }
    std::unreachable();
}

#undef FOR_EACH_FOOBAR

#undef DEFINE_ENUM_STR_CONVERSION
#undef DEFINE_ENUM_VAL

Это довольно легко расширить для любого поведения, которое может быть получено из имени типа перечисления и идентификаторов значений. Тем не менее, большую часть окружающего шаблона придется повторять для каждого типа перечисления, который вы хотите поддерживать (хотя я уверен, что это можно было бы обобщить немного дальше, если вы хотите избежать этого, за счет еще большей зависимости от магии препроцессора. ).

В частности, без отражения нет способа избежать (многословного и подверженного ошибкам) ​​повторения имен для создания строк из идентификаторов, кроме использования препроцессора.

Peter - Reinstate Monica 18.06.2024 17:17

Другие вопросы по теме

Возврат необязательного параметра из шаблонной функции преобразования
Достаточно ли предварительного объявления std::list, чтобы проверить, является ли тип T std::list?
Рекурсивный шаблон C++ с переменным числом вариантов
Ошибка компиляции при использовании функций шаблона C++, которые принимают в качестве аргументов другие функции, которые принимают ссылки на указатели
Специализация шаблона вне встроенного пространства имен функции, определенной внутри встроенного пространства имен
Как вывести в шаблон имя текущего тега? (Тот, в котором мы находимся)
Может ли быть нарушено ODR, если определение шаблона создается только с разными параметрами?
Вывести параметры шаблона для параметра шаблона std::variant
Функция constexpr, которая продолжает умножать число до тех пор, пока оно не станет «достаточно большим»
Требуется ли для создания экземпляров шаблонов классов использовать только указатель или ссылку на них?