forked from Alepha/Alepha
But the capabilities are now limited to a single param -- Time to change that?
174 lines
5.5 KiB
C++
174 lines
5.5 KiB
C++
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= HasCapability< 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= HasCapability< 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;
|
|
}
|