I wrote an Optional<T>
implementation in C++14 in order to support pre-C++17 compiler.
Differences to std::optional<T>
:
My implementation is not
explicit
-correct;Optional<T>
toOptional<U>
conversion is alwaysexplicit
even whenT
toU
conversion can be done implicitly. Similarly,U&&
toOptional<T>
conversion is always implicit. Getting this correct requires a lot of SFINAE hackery which I'm not particularly fond of.My implementation presupposes
.HasValue() == true
on access and generates UB if this requirement is not met. Hence, something likestd::bad_optional_access
does not exist.My implementation doesn't support
std::initializer_list
because I disagree withstd::initializer_list
and don't use it anywhere in my project.
I would greatly appreciate somebody else having a look at my code. I'm particularly unsure about the std::is_
traits and the noexcept
qualifiers.
Code:
#if __cplusplus == 201703L
#define SYMMAP_CPP17 1
#endif
#include <type_traits>
#include <utility>
#include <algorithm>
#include <functional>
#include <new>
#ifdef SYMMAP_CPP17
#define SYMMAP_LAUNDER( ... ) ( std::launder( __VA_ARGS__ ) )
#else
#define SYMMAP_LAUNDER( ... ) ( __VA_ARGS__ )
#endif
namespace Symmap {
struct NullOptT {
};
static constexpr NullOptT NullOpt;
struct InPlaceT {
};
static constexpr InPlaceT InPlace;
template< typename T >
class Optional;
template< typename T >
struct IsOptional : public std::false_type {
};
template< typename T >
struct IsOptional< Optional< T > > : public std::true_type {
};
template< typename T >
constexpr bool IsOptionalV = IsOptional< T >::value;
template< typename T >
class Optional {
static_assert( !std::is_lvalue_reference< T >::value, "T may not be a reference type" );
static_assert( !std::is_rvalue_reference< T >::value, "T may not be a reference type" );
static_assert( !std::is_const< T >::value, "T may not have top-level CV qualifiers" );
static_assert( !std::is_volatile< T >::value, "T may not have top-level CV qualifiers" );
static_assert( !IsOptionalV< T >, "T may not be an Optional" );
public:
using ValueType = T;
private:
using StorageT = std::aligned_storage_t< sizeof( ValueType ), alignof( ValueType ) >;
StorageT mStorage;
bool mHasValue = false;
void* GetStorage() noexcept
{
return reinterpret_cast< void* >( &mStorage );
}
template< typename... ArgsT >
void Construct( ArgsT&&... Args ) noexcept( std::is_nothrow_constructible< ValueType, ArgsT... >::value )
{
static_assert( std::is_constructible< ValueType, ArgsT... >::value, "ValueType cannot be constructed from ArgsT..." );
assert( !HasValue() );
new( GetStorage() ) ValueType( std::forward< ArgsT >( Args )... );
mHasValue = true;
}
/*
* We use a constexpr boolean instead of noexcept( Optional::Destruct() )
* Workaround for a longstanding GCC bug:
* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52869
*/
static constexpr bool NoexceptDestructible = std::is_nothrow_destructible< ValueType >::value;
void Destruct() noexcept( NoexceptDestructible )
{
if( HasValue() )
Value().ValueType::~ValueType();
}
public:
Optional() noexcept = default;
Optional( NullOptT ) noexcept
{
}
Optional( Optional const& Rhs ) noexcept( std::is_nothrow_copy_constructible< ValueType >::value )
{
static_assert( std::is_copy_constructible< ValueType >::value, "ValueType cannot be copy-constructed" );
if( Rhs.HasValue() )
Construct( Rhs.Value() );
}
Optional( Optional&& Rhs ) noexcept( std::is_nothrow_move_constructible< ValueType >::value )
{
static_assert( std::is_move_constructible< ValueType >::value, "ValueType cannot be move-constructed" );
if( Rhs.HasValue() )
Construct( std::move( Rhs.Value() ) );
}
template< typename U >
explicit Optional( Optional< U > const& Rhs ) noexcept( std::is_nothrow_constructible< ValueType, U const& >::value )
{
static_assert( std::is_constructible< ValueType, U const& >::value, "ValueType cannot be constructed from U const&" );
if( Rhs.HasValue() )
Construct( Rhs.Value() );
}
template< typename U >
explicit Optional( Optional< U >&& Rhs ) noexcept( std::is_nothrow_constructible< ValueType, U&& >::value )
{
static_assert( std::is_constructible< ValueType, U&& >::value, "ValueType cannot be constructed from U&&" );
if( Rhs.HasValue() )
Construct( std::move( Rhs.Value() ) );
}
template< typename... ArgsT >
Optional( InPlaceT, ArgsT&&... Args ) noexcept( std::is_nothrow_constructible< ValueType, ArgsT... >::value )
{
static_assert( std::is_constructible< ValueType, ArgsT... >::value, "ValueType cannot be constructed from ArgsT..." );
Construct( std::forward< ArgsT >( Args )... );
}
template< typename U = ValueType >
Optional( U&& Arg, std::enable_if_t< std::is_constructible< ValueType, U >::value >* = nullptr ) noexcept( std::is_nothrow_constructible< ValueType, U >::value )
{
// static_assert( std::is_constructible< ValueType, U >::value, "ValueType cannot be constructed from U" );
Construct( std::forward< U >( Arg ) );
}
~Optional() noexcept( NoexceptDestructible )
{
Destruct();
}
Optional& operator=( NullOptT ) noexcept( NoexceptDestructible )
{
Reset();
return *this;
}
Optional& operator=( Optional const& Rhs ) noexcept( NoexceptDestructible && std::is_nothrow_copy_assignable< ValueType >::value && std::is_nothrow_copy_constructible< ValueType >::value )
{
static_assert( std::is_copy_assignable< ValueType >::value, "ValueType cannot be copy-assigned" );
static_assert( std::is_copy_constructible< ValueType >::value, "ValueType cannot be copy-constructed" );
if( Rhs.HasValue() ) {
if( HasValue() )
Value() = Rhs.Value();
else
Construct( Rhs.Value() );
} else {
Reset();
}
return *this;
}
Optional& operator=( Optional&& Rhs ) noexcept( NoexceptDestructible && std::is_nothrow_move_assignable< ValueType >::value && std::is_nothrow_move_constructible< ValueType >::value )
{
static_assert( std::is_move_assignable< ValueType >::value, "ValueType cannot be move-assigned" );
static_assert( std::is_move_constructible< ValueType >::value, "ValueType cannot be move-constructed" );
if( Rhs.HasValue() ) {
if( HasValue() )
Value() = std::move( Rhs.Value() );
else
Construct( std::move( Rhs.Value() ) );
} else {
Reset();
}
return *this;
}
template< typename U >
Optional& operator=( Optional< U > const& Rhs ) noexcept( NoexceptDestructible && std::is_nothrow_assignable< ValueType, U const& >::value && std::is_nothrow_constructible< ValueType, U const& >::value )
{
static_assert( std::is_assignable< ValueType, U const& >::value, "ValueType cannot be assigned U const&" );
static_assert( std::is_constructible< ValueType, U const& >::value, "ValueType cannot be constructed from U const&" );
if( Rhs.HasValue() ) {
if( HasValue() )
Value() = Rhs.Value();
else
Construct( Rhs.Value() );
} else {
Reset();
}
return *this;
}
template< typename U >
Optional& operator=( Optional< U >&& Rhs ) noexcept( NoexceptDestructible && std::is_nothrow_assignable< ValueType, U&& >::value && std::is_nothrow_constructible< ValueType, U&& >::value )
{
static_assert( std::is_assignable< ValueType, U&& >::value, "ValueType cannot be assigned U&&" );
static_assert( std::is_constructible< ValueType, U&& >::value, "ValueType cannot be constructed from U&&" );
if( Rhs.HasValue() ) {
if( HasValue() )
Value() = std::move( Rhs.Value() );
else
Construct( std::move( Rhs.Value() ) );
} else {
Reset();
}
return *this;
}
template< typename U >
std::enable_if_t< std::is_constructible< ValueType, U >::value && std::is_assignable< ValueType, U >::value, Optional& > operator=( U&& Rhs ) noexcept( std::is_nothrow_constructible< ValueType, U >::value && std::is_nothrow_assignable< ValueType, U >::value )
{
// static_assert( std::is_assignable< ValueType, U >::value, "ValueType cannot be assigned U" );
// static_assert( std::is_constructible< ValueType, U >::value, "ValueType cannot be constructed from U" );
if( HasValue() )
Value() = std::forward< U >( Rhs );
else
Construct( std::forward< U >( Rhs ) );
return *this;
}
void Reset() noexcept( NoexceptDestructible )
{
if( HasValue() ) {
Destruct();
mHasValue = false;
}
}
template< typename... ArgsT >
ValueType& Emplace( ArgsT&&... Args ) noexcept( NoexceptDestructible && std::is_nothrow_constructible< ValueType, ArgsT... >::value )
{
static_assert( std::is_constructible< ValueType, ArgsT... >::value, "ValueType cannot be constructed from ArgsT..." );
Reset();
Construct( std::forward< ArgsT >( Args )... );
return Value();
}
bool HasValue() const noexcept
{
return mHasValue;
}
explicit operator bool() const noexcept
{
return HasValue();
}
ValueType& Value() & noexcept
{
assert( HasValue() );
return static_cast< ValueType& >( *SYMMAP_LAUNDER( reinterpret_cast< ValueType* >( &mStorage ) ) );
}
ValueType const& Value() const& noexcept
{
assert( HasValue() );
return static_cast< ValueType const& >( *SYMMAP_LAUNDER( reinterpret_cast< ValueType const* >( &mStorage ) ) );
}
ValueType&& Value() && noexcept
{
assert( HasValue() );
return static_cast< ValueType&& >( *SYMMAP_LAUNDER( reinterpret_cast< ValueType* >( &mStorage ) ) );
}
ValueType const&& Value() const&& noexcept
{
assert( HasValue() );
return static_cast< ValueType const&& >( *SYMMAP_LAUNDER( reinterpret_cast< ValueType const* >( &mStorage ) ) );
}
template< typename U >
ValueType ValueOr( U&& Fallback ) const& noexcept( std::is_nothrow_copy_constructible< ValueType >::value && std::is_nothrow_constructible< ValueType, U >::value )
{
static_assert( std::is_copy_constructible< ValueType >::value, "ValueType cannot be copy-constructed" );
static_assert( std::is_constructible< ValueType, U >::value, "ValueType cannot be constructed from U" );
if( HasValue() )
return Value();
else
return ValueType( std::forward< U >( Fallback ) );
}
template< typename U >
ValueType ValueOr( U&& Fallback ) && noexcept( std::is_nothrow_move_constructible< ValueType >::value && std::is_nothrow_constructible< ValueType, U >::value )
{
static_assert( std::is_move_constructible< ValueType >::value, "ValueType cannot be move-constructed" );
static_assert( std::is_constructible< ValueType, U >::value, "ValueType cannot be constructed from U" );
if( HasValue() )
return std::move( Value() );
else
return ValueType( std::forward< U >( Fallback ) );
}
const ValueType* operator->() const noexcept
{
return &Value();
}
ValueType* operator->() noexcept
{
return &Value();
}
ValueType const& operator*() const& noexcept
{
return Value();
}
ValueType& operator*() & noexcept
{
return Value();
}
ValueType const&& operator*() const&& noexcept
{
return Value();
}
ValueType&& operator*() && noexcept
{
return Value();
}
void Swap( Optional& Rhs ) noexcept( std::is_nothrow_move_constructible< ValueType >::value
#ifdef SYMMAP_CPP17
&& std::is_nothrow_swappable< ValueType >::value
#endif
)
{
using std::swap;
static_assert( std::is_move_constructible< ValueType >::value, "ValueType cannot be move-constructed" );
#ifdef SYMMAP_CPP17
static_assert( std::is_swappable< ValueType >::value, "ValueType cannot be swapped" );
#endif
if( Rhs.HasValue() ) {
if( HasValue() ) {
swap( Value(), Rhs.Value() );
} else {
Construct( std::move( Rhs.Value() ) );
Rhs.Reset();
}
} else {
if( HasValue() ) {
Rhs.Construct( std::move( Value() ) );
Reset();
} else {
// do nothing
}
}
}
};
template< typename T, typename U >
bool operator==( Optional< T > const& Lhs, Optional< U > const& Rhs ) noexcept( noexcept( std::declval< T const& >() == std::declval< U const& >() ) )
{
if( Lhs.HasValue() ^ Rhs.HasValue() )
return false;
if( !Lhs.HasValue() )
return true;
else
return Lhs.Value() == Rhs.Value();
}
// other comparison operators omitted for brevity's sake
template< typename T >
void swap( Optional< T >& Lhs, Optional< T >& Rhs ) noexcept( noexcept( Lhs.Swap( Rhs ) ) )
{
Lhs.Swap( Rhs );
}
template< typename T >
Optional< std::decay_t< T > > MakeOptional( T&& Arg ) noexcept( noexcept( Optional< std::decay_t< T > >{ std::forward< T >( Arg ) } ) )
{
return { std::forward< T >( Arg ) };
}
template< typename T, typename... ArgsT >
Optional< T > MakeOptional( ArgsT&&... Args )
{
return { InPlace, std::forward< ArgsT >( Args )... };
}
}
namespace std {
template< typename T >
struct hash< Symmap::Optional< T > > {
using argument_type = Symmap::Optional< T >;
using result_type = std::size_t;
result_type operator()( argument_type const& Object ) const
{
if( !Object ) {
return -1;
} else {
std::hash< T > Hasher;
return Hasher( *Object );
}
}
};
}
map
orflatmap
? \$\endgroup\$ – Alexander Sep 18 '17 at 21:20