Данный пост продолжает тему, начатую на прошлой неделе. В комментариях к первой заметке ув.тов.Константин подал идею об использовании операторов сравнения в виде свободных функций. Что показалось интересным и многообещающим.
Давеча удалось эту идею проверить. Результат мне понравился. ИМХО, так можно гораздо проще расширять функциональность шаблона 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 { template< typename, typename = std::void_t<> > struct has_less_then_comparison_v_fn : public std::false_type {}; template< typename 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 {}; template< typename, typename = std::void_t<> > struct has_equal_to_comparison_v_fn : public std::false_type {}; template< typename 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 {}; template< typename, typename = std::void_t<> > struct has_shift_to_stdostream_v_fn : public std::false_type {}; template< typename 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 // template< typename 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) ) {} template< typename... 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; } }; template< typename 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 ) noexcept( noexcept( std::less<V>{}( a.value(), b.value() ) ) ) { return std::less<V>{}( a.value(), b.value() ); } template< typename 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; template< typename 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 ) noexcept( noexcept( a.value() == b.value() ) ) { return a.value() == b.value(); } template< typename 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; template< typename 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 ) noexcept( noexcept( to << what.value() ) ) { return (to << what.value()); } template< typename 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" } }; for( const 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; for( const 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 } |
Можно проще написать, ваш вариант будет измучает компилятор:
ОтветитьУдалить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();
}
Извиняюсь, надо результат в tagged_value_t завернуть.
ОтветитьУдалить@sergegers:
ОтветитьУдалитьДа, можно сделать реализацию гораздо компактнее.
Но мне больше нравится информативность сообщений об ошибках в моем текущем варианте.
Вот здесь разница наглядно: https://gist.github.com/eao197/8e5524eb8b0e6688e310d34492d9267a