среда, 1 марта 2023 г.

[prog.c++] Продолжение экспериментов с шаблонами для улучшения tagged_value_t

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

Давеча удалось эту идею проверить. Результат мне понравился. ИМХО, так можно гораздо проще расширять функциональность шаблона tagged_value_t, чем в случае наследования от вспомогательных классов.

Получившееся решение показано под катом. Поиграться с ним можно на wandbox.

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

Ну а вот и сам код. Заодно сделал и оператор сдвига в std::ostream, поскольку это так же часто бывает удобно.

#include <iostream>
#include <set>
#include <string>

#include <functional>
#include <utility>
#include <type_traits>

//
// Begin of the implementation
//

namespace tagged_value_impl
{

templatetypenametypename = std::void_t<> >
struct has_less_then_comparison_v_fn : public std::false_type {};

templatetypename T >
struct has_less_then_comparison_v_fn<
      T,
      std::void_t< decltype( std::declval<const T&>() < std::declval<const T&>() ) >
   >
   : public std::true_type {};

templatetypenametypename = std::void_t<> >
struct has_equal_to_comparison_v_fn : public std::false_type {};

templatetypename T >
struct has_equal_to_comparison_v_fn<
      T,
      std::void_t< decltype( std::declval<const T&>() == std::declval<const T&>() ) >
   >
   : public std::true_type {};

templatetypenametypename = std::void_t<> >
struct has_shift_to_stdostream_v_fn : public std::false_type {};

templatetypename T >
struct has_shift_to_stdostream_v_fn<
      T,
      std::void_t< decltype( std::declval<std::ostream &>() << std::declval<const T&>() ) >
   >
   : public std::true_type {};

/* namespace tagged_value_impl */

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

public:
   using value_type = V;

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

   templatetypename... Args >
   tagged_value_t(
      std::piecewise_construct_t /*not_used*/,
      Args && ...args )
      : m_value{ std::forward<Args>(args)... }
   {}

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

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

templatetypename V, typename Tag >
[[nodiscard]]
std::enable_if_t<
      tagged_value_impl::has_less_then_comparison_v_fn< V >::value,
      bool >
operator<( const tagged_value_t<V, Tag> & a, const tagged_value_t<V, Tag> & b )
   noexceptnoexcept( std::less<V>{}( a.value(), b.value() ) ) )
{
   return std::less<V>{}( a.value(), b.value() );
}

templatetypename V, typename Tag >
[[nodiscard]]
std::enable_if_t<
      not tagged_value_impl::has_less_then_comparison_v_fn< V >::value,
      bool >
operator<( const tagged_value_t<V, Tag> & a, const tagged_value_t<V, Tag> & b ) = delete;

templatetypename V, typename Tag >
[[nodiscard]]
std::enable_if_t<
      tagged_value_impl::has_equal_to_comparison_v_fn< V >::value,
      bool >
operator==( const tagged_value_t<V, Tag> & a, const tagged_value_t<V, Tag> & b )
   noexceptnoexcept( a.value() == b.value() ) )
{
   return a.value() == b.value();
}

templatetypename V, typename Tag >
[[nodiscard]]
std::enable_if_t<
      not tagged_value_impl::has_equal_to_comparison_v_fn< V >::value,
      bool >
operator==( const tagged_value_t<V, Tag> & a, const tagged_value_t<V, Tag> & b ) = delete;

templatetypename V, typename Tag >
[[nodiscard]]
std::enable_if_t<
      tagged_value_impl::has_shift_to_stdostream_v_fn< V >::value,
      std::ostream & >
operator<<( std::ostream & to, const tagged_value_t<V, Tag> & what )
   noexceptnoexcept( to << what.value() ) )
{
   return (to << what.value());
}

templatetypename V, typename Tag >
[[nodiscard]]
std::enable_if_t<
      not tagged_value_impl::has_shift_to_stdostream_v_fn< V >::value,
      std::ostream & >
operator<<( std::ostream & to, const tagged_value_t<V, Tag> & what ) = delete;

//
// End of the implementation
//

//
// Begin of example code
//
using namespace std::string_literals;

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

struct simple_values_tag {};
using simple_ptr_t = tagged_value_t< int*, simple_values_tag >;

struct my_data
{
   std::string m_data;
};
struct binary_tag {};
using my_binaries_t = tagged_value_t< my_data, binary_tag >;

int main()
{
   ip_t a{ "192.168.1.1" };
   ip_t b{ "128.0.0.1" };

   std::cout << "a < b: " << (a < b) << std::endl;
   std::cout << "a > b: " << (b < a) << std::endl;
   std::cout << "a == b: " << (a == b) << std::endl;
   std::cout << "b == a: " << (b == a) << std::endl;
   std::cout << "a == a: " << (a == a) << std::endl;
   std::cout << "b == b: " << (b == b) << std::endl;

   std::set< ip_t > ips{
      ip_t{ "192.168.1.1" },
      ip_t{ "128.0.0.1" },
      ip_t{ "10.1.1.1" },
      { std::piecewise_construct, "192.168.100.1" },
      { std::piecewise_construct, "192.168.1.100" }
   };

   forconst auto & ip : ips )
      std::cout << "ip: " << ip << std::endl;

   int i1 = 0;
   char padding_1 = 0; (void)padding_1;
   int i2 = 3;
   char padding_2 = 0; (void)padding_2;
   int i3 = 4;

   std::set< simple_ptr_t > ints{
      simple_ptr_t{ &i2 },
      simple_ptr_t{ &i3 },
      simple_ptr_t{ &i1 }
   };

   std::cout << &i1 << " " << &i2 << " " << &i3 << std::endl;
   forconst auto & p : ints )
      std::cout << p << " ";
   std::cout << std::endl;

#if 0
   my_binaries_t bin1{ my_data{ "123456"s } };
   my_binaries_t bin2{ my_data{ "567890"s } };

   const auto cr = (bin1 < bin2);
   const auto er = (bin1 == bin2);
#endif
}

3 комментария:

sergegers комментирует...

Можно проще написать, ваш вариант будет измучает компилятор:

template< typename V, typename Tag >
[[nodiscard]] auto operator<( const tagged_value_t & a, const tagged_value_t & b ) noexcept( noexcept(a.value() < b.value())) -> decltype(a.value() < b.value())
{
return a.value() < b.value();
}

sergegers комментирует...

Извиняюсь, надо результат в tagged_value_t завернуть.

eao197 комментирует...

@sergegers:

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

Вот здесь разница наглядно: https://gist.github.com/eao197/8e5524eb8b0e6688e310d34492d9267a