пятница, 24 февраля 2023 г.

[life] Год в новой реальности

Да, уже год.

Тогда не верилось, что все это затянется на целый год.

Сейчас не верится, что все это закончится хотя бы года за три.

Мир явно меняется кардинально, и это явно не за день-два произойдет. Скорее уж лет пять, если не десять, а то и не пятнадцать.

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

вторник, 21 февраля 2023 г.

[prog.c++] Эксперимент с метапрограммрованием для создания более продвинутого варианта шаблона tagged_value_t

В языке C++ мне очень не хватает т.н. strong typedef. Временами хочется сказать, что вот этот вот int -- это ширина (width), а вот этот вот int -- это высота (height). Но в C++ штатных возможностей вывести разные несовместимые друг с другом типы из одного базового нет. Поэтому приходится велосипедить. Иногда просто вот так:

struct width_t { int value; }
struct height_t { int height; }

void fill(width_t w, height_t h) {...}

fill(width_t{640}, height_t{480});

Иногда и посложнее.

Для упрощения себе жизни когда-то сделал несложный вспомогательный тип tagged_value_t по типу вот такого:

templatetypename V, typename Tag >
class tagged_value_t
{
   V m_value;

public:
   explicit tagged_value_t( const V & value ) : m_value( value ) {}
   explicit tagged_value_t( V && value ) : m_value( std::forward<V>(value) ) {}

   [[nodiscard]]
   const V &
   value() const noexcept { return m_value; }

   [[nodiscard]]
   V &
   value() noexcept { return m_value; }
};

Этот тип кочует у меня из одного проекта в другой и позволяет делать что-то вроде:

struct url_tag {};
using url_t = tagged_value_t< std::string, url_tag >;

struct username_tag {};
using username_t = tagged_value_t< std::string, username_tag >;

struct raw_password_tag {};
using raw_password_t = tagged_value_t< std::string, raw_password_tag >;

struct base64_password_tag {};
using base64_password_tag = tagged_value_t< std::string, base64_password_tag >;

Подобные штуки применяю для указания типов параметров для функций/методов/конструкторов. В принципе удобно: на входе в функцию/метод ошибиться сложно, а внутри можно извлекать значения исходных типов (тех же int или std::string) и работать с ними привычными способами.

Но вот давеча захотелось сделать еще и так, чтобы значения, обернутые в tagged_value, можно было хранить в качестве ключей в ассоциативных контейнерах. Например, в качестве ключа для std::map.

Понятное дело, что для каждого tagged_value выписывать ручками operator< или operator== не хочется. Поэтому решил попробовать посредством метапрограммирования модифицировать tagged_value_t так, чтобы в нем появлялись operator< и/или operator== если таковые операторы определены для исходного типа.

Под катом то, что у меня получилось в первом приближении. Для C++17, т.к. это самый "свежий" стандарт, который я могу себе позволить. Возможно, концепты из C++20 позволят записать все это компактнее.