1
0
forked from Alepha/Alepha

Swappable facility.

This commit is contained in:
2023-11-10 23:27:06 -05:00
parent 96d09e3df2
commit 59a969da84

173
swappable.h Normal file
View File

@ -0,0 +1,173 @@
static_assert( __cplusplus > 2020'00 );
#pragma once
#include <Alepha/Alepha.h>
#include <type_traits>
#include "Capabilities.h"
#include "Concepts.h"
namespace Alepha::Hydrogen ::detail:: swappable_m
{
inline namespace exports
{
/*!
* Swappable capability hook.
*
* While it is possible to build `swap` in terms of `std::move` operations, it is often the case
* that `std::move` operations are built in terms of `std::swap`. This isn't a major problem,
* but when writing classes which manage a lot of state, and one has to write the lifecycle
* methods, it can become cumbersome to maintain the list of members that track through lifecycle
* transformations.
*
* Instead, similar to how `Alepha::comparable` works, `Alepha::swappable` is a hook-`Capability`
* which permits the user to write `swap_lens` as a member and the swap operation will be
* built in terms of that lens. This permits the list of members that need to be swapped to be
* written exactly once, in one place, with no repetition of members.
*
* Because `std::tie` objects are not swappable, the `Alepha::swap_magma` binding has been
* provided.
*
* Example:
* ```
* class Employee : Alepha::swappable
* {
* private:
* std::string firstName;
* std::string lastName;
*
* public:
* // With this member, `Employee` now has a fully built `swap` operation, which will
* // swap all of the listed members.
* auto swap_lens() noexcept { return Alepha::swap_magma( firstName, lastName ); }
*
* // The lifecycle methods of this class could now be implemented in terms of
* // `swap`, instead of manually listing large numbers of members to swap, or
* // calling a swap member which would have to be written.
* };
*```
* The primary benefit of using `swap_lens` is that the list of members to swap is not doubled-up,
* and the function signature is simpler. Otherwise this would be the normal approach:
* ```
* void
* swap( Employee &that ) noexcept
* {
* swap( this->firstName, that.firstName );
* swap( this->lastName, that.lastName );
* }
* ```
*
* The above approach is prone to error, especially when multiple members are of the same type.
* `swap( this->firstName, that.lastName )` is a common mistake, for example. Instead, the
* `swap_lens` permits the members to be listed only once, in a single canonical place.
*
* @note Due to a quirk of template metaprogramming and SFINAE with ADL, `swap_lens` must appear
* before any ADL usage of `swap` in the class which defines it. If `operator =( Self && )` will
* be defined in terms of this swap, `swap_lens` must be defined before `operator =( Self && )`.
*/
struct swappable {};
}
template< typename T >
concept HasMemberSwapLens=
requires( T t )
{
{ std::move( t ).swap_lens() };
};
template< typename T >
concept MemberSwapLensable= Capability< T, swappable > or HasMemberSwapLens< T >;
template< MemberSwapLensable T >
constexpr decltype( auto )
swap_lens( T &&item ) noexcept( noexcept( std::forward< T >( item ).swap_lens() ) )
{
return std::forward< T >( item ).swap_lens();
}
template< typename T >
concept SupportsSwapLens=
requires( T t )
{
{ swap_lens( std::move( t ) ) };
};
template< typename T >
concept SwapLensable= Capability< T, swappable > and SupportsSwapLens< T >;
// To compute the noexceptness of a swap expression, we
// have to render it as it would be called via ADL.
//
// Thus we make a namespace to guard all this mess
// and then expose the noexceptness of that expression
// as the noexceptness of a different function we can
// name.
namespace check_noexcept
{
using std::swap;
template< typename T >
constexpr void swap_check( T &&a, T &&b ) noexcept( noexcept( swap( std::forward< T >( a ), std::forward< T >( b ) ) ) );
}
template< typename T >
// requires( SwapLensable< T > )
constexpr void
swap( T &&a, T &&b ) noexcept //noexcept( noexcept( check_noexcept::swap_check( swap_lens( std::forward< T >( a ) ), swap_lens( std::forward< T >( b ) ) ) ) )
{
using std::swap;
return swap( swap_lens( std::forward< T >( a ) ), swap_lens( std::forward< T >( b ) ) );
}
// The swap binding and magma system allows one to specify a group of members (objects) which are suitable for the swap operation.
template< typename ... Args >
struct binding
{
std::tuple< Args... > data;
};
// Bindings have a complex swap implementation that recursively calls swap on those elements.
// This is necessary, since `std::tie` built tuples don't have a functioning swap operation.
template< std::size_t depth, typename ... Args >
constexpr void
swap_impl( std::tuple< Args... > &a, std::tuple< Args... > &b )
noexcept
(
( ... & noexcept( check_noexcept::swap_check( std::declval< Args & >(), std::declval< Args & >() ) ) )
)
{
using std::swap;
if constexpr( sizeof...( Args ) == depth ) return;
else
{
swap( std::get< depth >( a ), std::get< depth >( b ) );
return swap_impl< depth + 1 >( a, b );
}
}
template< typename ... Args >
constexpr void
swap( binding< Args... > a, binding< Args... > b ) noexcept( noexcept( swap_impl< 0 >( a.data, b.data ) ) )
{
return swap_impl< 0 >( a.data, b.data );
}
namespace exports
{
template< typename ... Args >
constexpr auto
swap_magma( Args && ... args ) noexcept
{
return binding< Args... >{ std::tie( std::forward< Args >( args )... ) };
}
}
}
namespace Alepha::Hydrogen::inline exports::inline swappable_m
{
using namespace detail::swappable_m::exports;
}