Русский
Русский
English
Статистика
Реклама

Enum

Из песочницы Перечисления в PHP

03.09.2020 22:12:42 | Автор: admin

В рамках описания предметной области распространены понятия с ограниченным числом значений. Для этого лучше всего подходят перечисления. В PHP нет специальных конструкций для описания перечисления, однако их можно имитировать при помощи объектно-ориентированного подхода.


Простейшая реализация


В простейшем случае реализовать перечисление можно как объект-обертку над простым типом, программно ограничив входящие аргументы. Как пример можно взять времена года, которых существует четыре и только четыре.


class Season{    public const SUMMER = 'summer';    public const AUTUMN = 'autumn';    public const WINTER = 'winter';    public const SPRING = 'spring';    private string $value;    public function __construct(string $value)    {        if (            self::SUMMER !== $value &&            self::AUTUMN !== $value &&            self::WINTER !== $value &&            self::SPRING !== $value        ) {            throw new InvalidArgumentException(sprintf(                "Wrong season value. Awaited '%s', '%s', '%s' or '%s'.",                self::SUMMER,                self::AUTUMN,                self::WINTER,                self::SPRING            ));        }        $this->value = $value;    }

Продемонстрировать процесс создания перечисления можно тестом.


    public function testCreation(): void    {        $summer = new Season(Season::SUMMER);        $autumn = new Season(Season::AUTUMN);        $winter = new Season(Season::WINTER);        $spring = new Season(Season::SPRING);        $this->expectException(InvalidArgumentException::class);        $wrongSeason = new Season('Wrong season');    }

Для получения внутреннего состояния можно реализовать метод-запрос. К примеру toValue() или getValue(). В случае, если внутреннее состояние описывается строкой, то для его получения идеально подходит магический метод __toString().


    public function __toString(): string    {        return $this->value;    }

Реализация __toString() даёт возможность исользовать объект-перечисление напрямую в конкатенациях строк и в виде строковых аргументов методов.


    public function testStringConcatenation(): void    {        $autumn = new Season(Season::AUTUMN);        $spring = new Season(Season::SPRING);        $value = $autumn . ' ' . $spring;        $this->assertIsString($value);        $this->assertSame(Season::AUTUMN . ' ' . Season::SPRING, $value);    }

В PHP два объекта равны, если они имеют одинаковые атрибуты и значения. Однако при использовании тождественного равенства переменные, содержащие объекты, считаются идентичными только тогда, когда они ссылаются на один и тот же экземпляр одного и того же класса.


    public function testEquality(): void    {        $firstSummer = new Season(Season::SUMMER);        $secondSummer = new Season(Season::SUMMER);        $winter = new Season(Season::WINTER);        $this->assertTrue($firstSummer == $secondSummer);        $this->assertFalse($firstSummer == $winter);        $this->assertFalse($firstSummer === $secondSummer);        $this->assertFalse($firstSummer === $winter);    }

Для обеспечения интуитивно предсказуемого результата сравнения можно реализовать метод equals.


    public function equals(Season $season): bool    {        return $this->value === $season->value;    }

    public function testEquals(): void    {        $firstSummer = new Season(Season::SUMMER);        $secondSummer = new Season(Season::SUMMER);        $firstWinter = new Season(Season::WINTER);        $secondWinter = new Season(Season::WINTER);        $this->assertTrue($firstSummer->equals($secondSummer));        $this->assertTrue($firstWinter->equals($secondWinter));        $this->assertFalse($firstSummer->equals($secondWinter));    }

Можно обратить внимание на тавтологию при создании экземпляров перечисления: слово Season повторяется дважды. Её можно избежать если для создания использовать статические методы.


Предположим: в магазине аудиотехники продаются микрофоны. В ассортименте представлены модели использующих для подключения xlr 3pin, jack, mini jack и usb разъёмы.


class MicrophoneConnector{    public const XLR_3PIN = 'xlr_3pin';    public const JACK = 'jack';    public const MINI_JACK = 'mini_jack';    public const USB = 'usb';    private string $value;    private function __construct(string $value)    {        $this->value = $value;    }    public function __toString(): string    {        return $this->value;    }

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


    public static function xlr3pin(): self    {        return new self(self::XLR_3PIN);    }

По аналогии необходимо реализовать методы jack, miniJack и usb.


    public function testEquality(): void    {        $firstJack = MicrophoneConnector::jack();        $secondJack = MicrophoneConnector::jack();        $xlr3pin = MicrophoneConnector::xlr3pin();        $this->assertTrue($firstJack == $secondJack);        $this->assertFalse($firstJack == $xlr3pin);        $this->assertFalse($firstJack === $secondJack);        $this->assertFalse($firstJack === $xlr3pin);    }

Перечисление как одиночка


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


Предположим: в организацию приходит заказ на оказание некоторой услуги. Заказ может быть принят, отвергнут либо, при возникновении нестандартной ситуации, решение может быть отложено для выяснения обстоятельств. Решение можно описать как перечисление из значений agree, disagree и hold.


class Decision{    public const AGREE = 'agree';    public const DISAGREE = 'disagree';    public const HOLD = 'hold';    private string $value;    private function __construct(string $value)    {        $this->value = $value;    }    private function __clone() { }    public function __toString(): string    {        return $this->value;    }

С целью предотвращения создания нескольких экземпляров обьекта необходимо заблокировать конструктор и магический метод __clone(). Для каждого варианта перечисления реализуется статический метод.


    private static $agreeInstance = null;    public static function agree(): self    {        if (null === self::$agreeInstance) {            self::$agreeInstance = new self(self::AGREE);        }        return self::$agreeInstance;    }

По аналогии c agree реализуются методы disagree и hold.


    public function testEquality(): void    {        $firsAgree = Decision::agree();        $secondAgree = Decision::agree();        $firstDisagree = Decision::disagree();        $secondDisagree = Decision::disagree();        $this->assertTrue($firsAgree == $secondAgree);        $this->assertTrue($firstDisagree == $secondDisagree);        $this->assertFalse($firsAgree == $secondDisagree);        $this->assertTrue($firsAgree === $secondAgree);        $this->assertTrue($firstDisagree === $secondDisagree);        $this->assertFalse($firsAgree === $secondDisagree);    }

Такая реализация позволяет сравнивать перечисления напрямую. А в купе с реализацией __toString() обеспечивает интуитивную работу с перечислением как с простым типом. Но у такого подхода есть серьёзный недостаток: всё равно есть возможность создать новый экземпляр объекта с помощью десериализации. Для обеспечения корректной десериализации придётся добавить механизм создания экземпляра из произвольного значения.


    public static function from($value): self    {        switch ($value) {            case self::AGREE:                return self::agree();            case self::DISAGREE:                return self::disagree();            case self::HOLD:                return self::hold();            default:                throw new InvalidArgumentException(sprintf(                    "Wrong decision value. Awaited '%s', '%s' or '%s'.",                    self::AGREE,                    self::DISAGREE,                    self::HOLD                ));        }    }

Предположим: простейший заказ можно описать как пояснение к заказу и решение принятое по нему. Между запросами существует необходимость сохранять заказ в кэш. Для этого можно воспользоваться стандартным механизмом сериализации: реализовать магические методы __sleep() и __wakeup(). В методе __sleep() перечислить поля для сериализации. В методе __wakeup() восстановить конкретный экземпляр при помощи статического метода from() класса Decision.


class Order{    private Decision $decision;    private string $description;    public function getDecision(): Decision    {        return $this->decision;    }    public function getDescription(): string    {        return $this->description;    }    public function __construct(Decision $decision, string $description)    {        $this->decision = $decision;        $this->description = $description;    }    public function __sleep(): array    {        return ['decision', 'description'];    }    public function __wakeup(): void    {        $this->decision = Decision::from($this->decision);    }}

Процесс сериализации/десериализации можно представить в виде теста.


    public function testSerialization(): void    {        $order = new Order(Decision::hold(), 'Some order description');        $serializedOrder = serialize($order);        $this->assertIsString($serializedOrder);        /** @var Order $unserializedOrder */        $unserializedOrder = unserialize($serializedOrder);        $this->assertInstanceOf(Order::class, $unserializedOrder);        $this->assertTrue($order->getDecision() === $unserializedOrder->getDecision());    }

Перечисление с большим числом вариантов


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


class Season{    public const SEASONS = ['summer', 'autumn', 'winter', 'spring'];    private string $value;    public function __construct(string $value)    {        if (!in_array($value, self::SEASONS)) {            throw new InvalidArgumentException(sprintf(                "Wrong season value. Awaited one from: '%s'.",                implode("', '", self::SEASONS)            ));        }        $this->value = $value;    }

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


Предположим: в магазине одежды продают одежду разных размеров. Тогда возможные размеры можно описать перечислением.


class Size{    public const SIZES = ['xxs', 'xs', 's', 'm', 'l', 'xl', 'xxl'];    private string $value;    private function __construct(string $value)    {        $this->value = $value;    }    public function __toString(): string    {        return $this->value;    }    public static function __callStatic($name, $arguments)    {        $value = strtolower($name);        if (!in_array($value, self::SIZES)) {            throw new BadMethodCallException("Method '$name' not found.");        }        if (count($arguments) > 0) {            throw new InvalidArgumentException("Method '$name' expected no arguments.");        }        return new self($value);    }}

    public function testEquality(): void    {        $firstXxl = Size::xxl();        $secondXxl = Size::xxl();        $firstXxs = Size::xxs();        $secondXxs = Size::xxs();        $this->assertTrue($firstXxl == $secondXxl);        $this->assertTrue($firstXxs == $secondXxs);        $this->assertFalse($firstXxl == $secondXxs);        $this->assertFalse($firstXxl === $secondXxl);        $this->assertFalse($firstXxs === $secondXxs);        $this->assertFalse($firstXxl === $secondXxs);    }

Недостатком использования такого подхода является, то, что синтаксический анализатор IDE не может распознать методы вызываемые через __callStatic(). Методы приходится описывать в DocBlock'ах, что практически сводит на нет пользу от обобщения кода.


/** * @method static Size xxs() * @method static Size xs() * @method static Size s() * @method static Size m() * @method static Size l() * @method static Size xl() * @method static Size xxl() */class Size{

Критика других реализаций


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


В PECL-пакете SPL есть класс SplEnum. Однако SPL пакет может быть не установлен в исполняющей среде (либо его и не хочется ставить).


Заключение


В PHP нет специальных конструкций для описания перечисления, однако их можно имитировать при помощи объектно-ориентированного подхода. Использование перечисления как одиночки хоть и даёт некоторые преимущества, имеет критический недостаток значительно усложняющий реализацию. Стоит заметить, что это не столько проблема реализации перечисления, сколько общая проблема реализации паттерна "одиночка" в PHP. Обобщенные решения практически не дают преимуществ, так как конкретные методы всё равно приходится описывать в DocBlock'ах.


Все примеры на GitHub.

Подробнее..

Из песочницы Enum и switch, и что с ними не так

04.09.2020 16:11:02 | Автор: admin

image


Часто ли у вас было такое, что вы добавляли новое значение в enum и потом тратили часы на то, чтобы найти все места его использования, а затем добавить новый case, чтобы не получить ArgumentOutOfRangeException во время исполнения?


Идея


Если проблема состоит только в switch операторе и отслеживании новых типов, тогда давайте избавимся от них!


Идея состоит в том, чтобы заменить использование switch паттерном visitor.


Пример 1


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


Определим файл DocumentType.cs:


public enum DocumentType{    Invoice,    PrepaymentAccount}public interface IDocumentVisitor<out T>{    T VisitInvoice();    T VisitPrepaymentAccount();}public static class DocumentTypeExt{    public static T Accept<T>(this DocumentType self, IDocumentVisitor<T> visitor)    {        switch (self)        {            case DocumentType.Invoice:                return visitor.VisitInvoice();            case DocumentType.PrepaymentAccount:                return visitor.VisitPrepaymentAccount();            default:                throw new ArgumentOutOfRangeException(nameof(self), self, null);        }    }}

И да, я предлагаю определять все связанные типы в одном файле, что не является идиоматичным для .Net разработчика. Но иногда это очень ухудшает упрощает понимание кода.


Опишем visitor который будет искать в базе нужный документ DatabaseSearchVisitor.cs:


public class DatabaseSearchVisitor : IDocumentVisitor<IDocument>{    private ApiId _id;    private Database _db;    public DatabaseSearchVisitor(ApiId id, Database db)    {        _id = id;        _db = db;    }    public IDocument VisitInvoice() => _db.SearchInvoice(_id);    public IDocument VisitPrepaymentAccount() => _db.SearchPrepaymentAccount(_id);}

И потом его использование:


public void UpdateStatus(ApiDoc doc){    var searchVisitor = new DatabaseSearchVisitor(doc.Id, _db);    var databaseDocument = doc.Type.Accept(searchVisitor);    databaseDocument.Status = doc.Status;    _db.SaveChanges();}

Пример 2


У нас есть события, которые выглядят следующим образом:


public enum PurseEventType{    Increase,    Decrease,    Block,    Unlock}public sealed class PurseEvent{    public PurseEventType Type { get; }    public string Json { get; }    public PurseEvent(PurseEventType type, string json)    {        Type = type;        Json = json;    }}

Мы хотим отправлять уведомления пользователю на определенный тип событий. Тогда реализуем visitor:


public interface IPurseEventTypeVisitor<out T>{    T VisitIncrease();    T VisitDecrease();    T VisitBlock();    T VisitUnlock();}public sealed class PurseEventTypeNotificationVisitor : IPurseEventTypeVisitor<Missing>{    private readonly INotificationManager _notificationManager;    private readonly PurseEventParser _eventParser;    private readonly PurseEvent _event;    public PurseEventTypeNotificationVisitor(PurseEvent @event, PurseEventParser eventParser, INotificationManager notificationManager)    {        _notificationManager = notificationManager;        _event = @event;        _eventParser = eventParser;    }    public Missing VisitIncrease() => Missing.Value;    public Missing VisitDecrease() => Missing.Value;    public Missing VisitBlock()    {        var blockEvent = _eventParser.ParseBlock(_event);        _notificationManager.NotifyBlockPurseEvent(blockEvent);        return Missing.Value;    }    public Missing VisitUnlock()    {        var blockEvent = _eventParser.ParseUnlock(_event);        _notificationManager.NotifyUnlockPurseEvent(blockEvent);        return Missing.Value;    }}

Для примера не будем ничего возвращать. Для этого можно воспользоваться типом Missing из System.Reflection или же написать тип Unit. В реальном проекте возвращался бы Result, например, с информацией об ошибке, если такие имеются.


И пример использования:


public void SendNotification(PurseEvent @event){    var notificationVisitor = new PurseEventTypeNotificationVisitor(@event, _eventParser, _notificationManager);    @event.Type.Accept(notificationVisitor);}

Дополнение


Если нужно быстрее


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


Метод расширение:


public static T Accept<TVisitor, T>(this DocumentType self, in TVisitor visitor)    where TVisitor : IDocumentVisitor<T>    {        switch (self)        {            case DocumentType.Invoice:                return visitor.VisitInvoice();            case DocumentType.PrepaymentAccount:                return visitor.VisitPrepaymentAccount();            default:                throw new ArgumentOutOfRangeException(nameof(self), self, null);        }    }

Сам visitor остаётся прежним, только меняем class на struct.


И сам код обновления документа выглядит не так удобно, но работает быстро:


public void UpdateStatus(ApiDoc doc){    var searchVisitor = new DatabaseSearchVisitor(doc.Id, _db);    var databaseDocument = doc.Type.Accept<DatabaseSearchVisitor, IDocument>(searchVisitor);    databaseDocument.Status = doc.Status;    _db.SaveChanges();}

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


Читабельность и in-place реализация


Если нужно реализовать логику только в одном месте, то часто visitor громоздко и не удобно. Поэтому есть альтернативное решение match.


Сразу пример со структурой:


public static T Match<T>(this DocumentType self, Func<T> invoiceCase, Func<T> prepaymentAccountCase){    var visitor = new FuncVisitor<T>(invoiceCase, prepaymentCase);    return self.Accept<FuncVisitor<T>, T>(visitor);}

Сам FuncVisitor:


public readonly struct FuncVisitor<T> : IDocumentVisitor<T>{    private readonly Func<T> _invoiceCase;    private readonly Func<T> _prepaymentAccountCase;    public FuncVisitor(Func<T> invoiceCase, Func<T> prepaymentAccountCase)    {        _invoiceCase = invoiceCase;        _prepaymentAccountCase = prepaymentAccountCase;    }    public T VisitInvoice() => _invoiceCase();    public T VisitPrepaymentAccount() => _prepaymentAccountCase();}

Использование match:


public void UpdateStatus(ApiDoc doc){    var databaseDocument = doc.Type.Match(        () => _db.SearchInvoice(doc.Id),        () => _db.SearchPrepaymentAccount(doc.Id)    );    databaseDocument.Status = doc.Status;    _db.SaveChanges();}

Итог


При добавлении нового значения в enum необходимо:


  1. Добавить метод в интерфейс.
  2. Добавить его использование в метод расширение.

Для остальных мест компилятор подскажет нам, где необходимо реализовать новый метод.
Таким образом мы избавляемся от проблемы забытого case в switch.


Это все еще не серебряная пуля, но может здорово помочь в работе с enum.


Ссылки


Подробнее..

Enum в PHP 8.1 для чего нужен enum, и как реализован в PHP

07.02.2021 18:17:06 | Автор: admin

Через несколько дней заканчивается голосование по первой итерации реализации enum в PHP 8.1 . Уже видно, что голосов за гораздо больше, так что давайте кратко пройдемся и посмотрим, что же нам приготовили авторы языка.


Зачем нужны enum?


Зачем вообще нужны enum? По сути они служат цели улучшенного описания типов. Давайте рассмотрим пример без енумов и с ними. Допустим, у нас продаются машины трех цветов: красные, черные и белые. Как описать цвет, какой тип выбрать?


class Car {    private string $color;    function setColor(string $color): void {        $this->color = $color;    }}

Если мы опишем цвет машины как простой string, то во-первых при вызове $myCar->setColor(..) непонятно, что за строку туда писать. red или RED или #ff0000, а во вторых, легко ошибиться, просунув туда случайно что-то не то (пустую строку, к примеру). То же самое будет, если использовать не строки, а числа, например.


Это приводит к тому, что многие php-программисты заводят константы и объединяют их в одном классе, чтобы явно видеть все варианты.


class Color {    public const RED   = "red";    public const BLACK = "black";    public const WHITE = "white";}

и задавая цвет, пишут $myCar->setColor(Color::RED);


Уже лучше. Но если мы впервые видим метод $myCar->setColor(...), мы можем и не знать, что где-то есть константы для цветов. И мы всё еще можем сунуть туда любую строку без какого-либо сообщения об ошибке.


Поэтому здесь нужен не класс, а отдельный тип. Этот тип называется enum


Pure enum


В самом простом случае (pure enum), enum описывается так:


enum Color {    case Red;    case Black;    case White;}

Описав такой тип, мы можем использовать его везде:


class Car {    private Color $color;    function setColor(Color $color): void {        $this->color = $color;    }}

Из сигнатуры метода всё сразу видно, какие варианты есть. И метод setColor можно использовать только так: $myCar->setColor(Color::White), никаких строк и доморощенных списков констант. Ничего лишнего не сунешь. Читабельность и поддерживаемость кода стала выше.


Каждый из case-ов (Color::Red, Color::Black, Color::White) является объектом типа Color (можно проверить через instanseof ). Т.е. под капотом это не числа 0,1,2 как в некоторых языках, а именно объекты. Их нельзя сравнивать оператором >, например. У каждого такого объекта есть встроенное свойство $name:


print Color::Red->name; // вернет строку Red

Enum со скалярами


Но если бы это было всё, то было бы слишком просто. После названия enum можно указать скалярный тип, например string. И у каждого кейса указать скалярное значение. Это может быть полезно для некоторых целей, например для сортировки или записи в базу данных.


enum Color: string {    case Red = "R";    case Black = "B";    case White = "W";}

Скалярное значение можно получить потом так:


Color::Red->value  //вернет строку R

И наоборот, т.е. получить case через значение, тоже можно:


Color::from("R") // вернет объект Color::Red

Помимо полей "case" в enum может быть еще много всего. По сути это разновидность класса. Там могут быть методы, он может реализовывать интерфейсы или использовать трейты.


Пример из RFC.


interface Colorful {  public function color(): string;}trait Rectangle {  public function shape(): string {    return "Rectangle";  }}enum Suit implements Colorful {  use Rectangle;  case Hearts;  case Diamonds;  case Clubs;  case Spades;  public function color(): string {    return match($this) {      Suit::Hearts, Suit::Diamonds => 'Red',      Suit::Clubs, Suit::Spaces => 'Black',    };  }}

При этом $this будет тот конкретный объект case, для которого мы вызываем метод.


Кстати, обратите внимание на работу с енамами в конструкции match. Похоже, match затевался именно для них.


Лично я горячо одобряю введение enum в PHP, это очень удобно и читабельно, и в большинстве языков, где есть какие-никакие типы, enum уже давно есть (кроме, разве что Go).


Дальше больше. Tagged Unions (тип-сумма)


Есть RFC, которые развивают идею enums дальше, чтобы можно было хранить в одном enum значения разных типов. Как в языке Rust, например. Тогда можно будет сделать, допустим, enum Result с двумя case-ами Result::Ok и Result::Err, причем эти объекты будут хранить данные: Ok будет хранить результат, а Err ошибку, у каждого из этих значений будет свой тип.


И всё это не в Расте или Хаскеле, а в PHP!


Об этом мы расскажем в деталях в ближайших постах телеграм-канала Cross Join, не забудьте подписаться.

Подробнее..

C enum lt-gt string? Легко

12.09.2020 20:07:24 | Автор: admin

Вот, скажем, один из самых популярных примеров. Можно сказать, классических. Сериализуются данные в, скажем, json. В структуре есть enum-поле, которое хочется сохранять в текстовом виде (а не числом). Всё. Стоп. Простого способа решить эту элементарную задачу на C++ не существует. (c)

... Но очень хочется.

За последний год я видел, как чуть ли не в каждом проекте разработчик предлагал своё видение этой проблемы. И везде было дублирование кода, везде какие-то костыли, и "тонкости". Да что уж там, мне самому приходится время от времени возвращаться к этой теме. Хватит. Решил раз и навсегда закрыть вопрос, по крайней мере для себя.

Код далёк от совершенства (надеюсь,анонимуспоправит), но свою задачу выполняет. Может кому ипригодится:

Реализация
// enum_string.h#pragma once#define DECLARE_ENUM(T, values...)                                    \  enum class T { values, MAX };                                       \  char enum_##T##_base[sizeof(#values)] = #values;                    \  const char* T##_tokens[static_cast<__underlying_type(T)>(T::MAX)];  \  const char* const* T##_tmp_ptr = tokenize_enum_string(              \      const_cast<char*>(enum_##T##_base), sizeof(#values), T##_tokens,\      static_cast<__underlying_type(T)>(T::MAX));#define enum_to_string(T, value) \  (T##_tokens[static_cast<__underlying_type(T)>(value)])static const char* const* tokenize_enum_string(char* base,                                               int length,                                               const char* tokens[],                                               int size) {  int count = 0;  tokens[count++] = base;  for (int i = 1; i < length; ++i) {    if (base[i] == ',') {      base[i] = '\0';      if (count == size) {        return tokens;      }      do {        if (++i == length) {          return tokens;        }      } while (' ' == base[i]);      tokens[count++] = base + i;    }  }  return tokens;}static bool string_equals(const char* a, const char* b) {  int i = 0;  for (; a[i] && b[i]; ++i) {    if (a[i] != b[i]) {      return false;    }  }  return (a[i] == b[i]);}static int string_to_enum_int(const char* const tokens[], int max,                              const char* value) {  for (int i = 0; i < max; ++i) {    if (string_equals(tokens[i], value)) {      return i;    }  }  return max;}#define string_to_enum(T, value)     \  static_cast<T>(string_to_enum_int( \      T##_tokens, static_cast<__underlying_type(T)>(T::MAX), value))

Работу со строками можете без проблем заменить на ваши любимые библиотеки, большинство кода здесь - это как раз парсинг строки (уж очень хотелось обойтись без STL).

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

пример использования
// main.cpp#include <iostream>#include "enum_string.h"DECLARE_ENUM(LogLevel,  // enum class LogLevel             Alert,     // LogLevel::Alert             Critical,  // LogLevel::Critical             Error,     // LogLevel::Error             Warning,   // LogLevel::Warning             Notice,    // LogLevel::Notice             Info,      // LogLevel::Info             Debug      // LogLevel::Debug             );int main() {  // serialize  LogLevel a = LogLevel::Critical;  std::cout << enum_to_string(LogLevel, a) << std::endl;  // deserialize  switch (string_to_enum(LogLevel, "Notice")) {    case LogLevel::Alert: {      std::cout << "ALERT" << std::endl;    } break;    case LogLevel::Critical: {      std::cout << "CRITICAL" << std::endl;    } break;    case LogLevel::Error: {      std::cout << "ERROR" << std::endl;    } break;    case LogLevel::Warning: {      std::cout << "WARN" << std::endl;    } break;    case LogLevel::Notice: {      std::cout << "NOTICE" << std::endl;    } break;    case LogLevel::Info: {      std::cout << "INFO" << std::endl;    } break;    case LogLevel::Debug: {      std::cout << "DEBUG" << std::endl;    } break;    case LogLevel::MAX: {      std::cout << "Incorrect value" << std::endl;    } break;  }  return 0;}

Как по мне, в дополнительном обьяснении не нуждается.

Также, залил на github.

Любезно приглашаю критиков на ревью.

Подробнее..
Категории: C++ , Enum , Serialization

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru