В языке 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 по типу вот такого:
template< typename 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 позволят записать все это компактнее.