This is an implementation of optional<T> from C++17, it is mostly standard conforming.
I'm looking for a review on efficiency, correctness and performance.
Sample usage
#include "optional.h"
int main()
{
util::optional<std::string> s0{ "abc" };
util::optional<std::string> s1;
if ( s1 ) s0 = s1;
else s1 = s0;
if ( s0 == s1 && s0 == std::string{ "abc" } )
{
s1 = "def";
s0 = util::nullopt;
}
using std::swap;
swap( s1, s0 );
std::cout << *s0 << '\n';
std::cout << s1.value_or( "default" ) << '\n';
}
Implementation
optional.h
#ifndef UTIL_OPTIONAL_H
#define UTIL_OPTIONAL_H
#include <new>
#include <type_traits>
#include "optional_comparison.h"
namespace util
{
// 20.5.4, in-place construction
struct in_place_t {};
constexpr in_place_t in_place{};
// 20.5.5, no-value state indicator
struct nullopt_t
{
constexpr nullopt_t( int const ) noexcept {};
};
constexpr nullopt_t nullopt{ 0 };
// 20.5.6, class bad_optional_access
class bad_optional_access : public std::logic_error
{
public:
bad_optional_access() : logic_error( "uninitialized optional" ) {}
};
// 20.5.3, optional for object types
template <typename T>
class optional
{
private:
struct conversion_ctor_t {};
static constexpr conversion_ctor_t conversion_ctor{};
public:
template <typename U> friend class optional;
using value_type = T;
// 20.5.3.2, destructor
// issue: not trivially destructible if T is trivially destructible
~optional() noexcept( std::is_nothrow_destructible<T>::value )
{
destroy_value();
}
// 20.5.3.1, constructors
// issue: vc++2015 update 2 cannot declare the following as constexpr:
// optional();
// optional( nullopt_t );
// optional( optional const& );
// optional( optional&& );
// optional( T const& );
// optional( T&& );
constexpr optional() noexcept : m_has_value{ false } {}
constexpr optional( nullopt_t ) noexcept : m_has_value{ false } {}
constexpr optional( optional const& other )
noexcept( std::is_nothrow_copy_constructible<T>::value )
: m_has_value{ other.m_has_value }
{
if ( m_has_value )
{
emplace( other.m_value );
}
}
constexpr optional( optional&& other )
noexcept( std::is_nothrow_move_constructible<T>::value )
: m_has_value{ other.m_has_value }
{
if ( m_has_value )
{
emplace( std::move( other.m_value ) );
}
}
constexpr optional( T const& value )
noexcept( std::is_nothrow_copy_constructible<T>::value )
: optional( in_place, value )
{}
constexpr optional( T&& value )
noexcept( std::is_nothrow_move_constructible<T>::value )
: optional( in_place, std::move( value ) )
{}
template <typename... Args>
constexpr optional( in_place_t, Args&&... args )
noexcept( std::is_nothrow_constructible<T, Args&&...>::value )
: m_has_value{ true }
, m_value( std::forward<Args>( args )... )
{}
template
<
typename U,
typename... Args,
std::enable_if_t
<
std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value, int
> = 0
>
constexpr optional( in_place_t, std::initializer_list<U> il, Args&&... args )
noexcept( std::is_nothrow_constructible<U,
std::initializer_list<U>&, Args&&...>::value )
: m_has_value{ true }
, m_value( il, std::forward<Args>( args )... )
{}
// 20.5.3.3, assignment
optional& operator=( nullopt_t )
noexcept( std::is_nothrow_destructible<T>::value )
{
destroy_value();
m_has_value = false;
return *this;
}
optional& operator=( optional const& other )
noexcept( std::is_nothrow_copy_constructible<T>::value &&
std::is_nothrow_copy_assignable<T>::value )
{
if ( static_cast<void*>( this ) != &other )
{
if ( other.m_has_value )
{
set_value( other.m_value );
}
else
{
destroy_value();
m_has_value = false;
}
}
return *this;
}
optional& operator=( optional&& other )
noexcept( std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value )
{
if ( static_cast<void*>( this ) != &other )
{
if ( other.m_has_value )
{
set_value( std::move( other.m_value ) );
}
else
{
destroy_value();
m_has_value = false;
}
}
return *this;
}
template
<
typename U,
std::enable_if_t<std::is_same<std::decay_t<U>, T>::value, int> = 0
>
optional& operator=( U&& value )
noexcept( std::is_nothrow_assignable<T, U&&>::value )
{
set_value( std::forward<U>( value ) );
return *this;
}
template <typename... Args>
void emplace( Args&&... args )
noexcept( std::is_nothrow_constructible<T, Args&&...>::value )
{
*this = nullopt;
::new ( &m_value ) T( std::forward<Args>( args )... );
m_has_value = true;
}
template
<
typename U,
typename... Args,
std::enable_if_t
<
std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value, int
> = 0
>
void emplace( std::initializer_list<U> il, Args&&... args )
noexcept( std::is_nothrow_constructible<T,
std::initializer_list<U>&, Args&&...>::value )
{
*this = nullopt;
::new ( &m_value ) T( il, std::forward<Args>( args )... );
m_has_value = true;
}
// 20.5.3.4, swap
void swap( optional& other )
noexcept( std::is_nothrow_move_constructible<T>::value &&
noexcept( std::swap( std::declval<T&>(), std::declval<T&>() ) ) )
{
if ( this != &other )
{
if ( m_has_value && other.m_has_value )
{
using std::swap;
swap( m_value, other.m_value );
}
else if ( m_has_value )
{
other.m_has_value = true;
other.emplace( std::move( m_value ) );
destroy_value();
m_has_value = false;
}
else if ( other.m_has_value )
{
m_has_value = true;
emplace( std::move( other.m_value ) );
other.destroy_value();
other.m_has_value = false;
}
}
}
// 20.5.3.5, observers
// issue 1: vc++2015 update 2 cannot declare the following as constexpr:
// T* operator->() noexcept;
// T const* operator->() const noexcept;
// T& operator*() & noexcept;
// T const& operator*() const & noexcept;
// T&& operator*() && noexcept;
// T const&& operator*() const && noexcept;
// issue 2: issue 1 + cannot be implemented as single line return statement
// T& value() &;
// T const& value() const &;
// T&& value() &&;
// T const&& value() const &&;
constexpr T* operator->() noexcept { return &m_value; }
constexpr T const* operator->() const noexcept { return &m_value; }
constexpr T& operator*() & noexcept { return m_value; }
constexpr T const& operator*() const & noexcept { return m_value; }
constexpr T&& operator*() && noexcept { return m_value; }
constexpr T const&& operator*() const && noexcept { return m_value; }
constexpr explicit operator bool() const noexcept { return m_has_value; }
constexpr T& value() &
{
if ( m_has_value ) return m_value;
throw bad_optional_access{};
}
constexpr T const& value() const&
{
if ( m_has_value ) return m_value;
throw bad_optional_access{};
}
constexpr T&& value() &&
{
if ( m_has_value ) return std::move( m_value );
throw bad_optional_access{};
}
constexpr T const&& value() const&&
{
if ( m_has_value ) return std::move( m_value );
throw bad_optional_access{};
}
template <typename U>
constexpr auto value_or( U&& default_value ) const &
noexcept( std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_constructible<T, U&&>::value )
{
return m_has_value ?
m_value : static_cast<T>( std::forward<U>( default_value ) );
}
template <typename U>
constexpr T value_or( U&& default_value ) &&
noexcept( std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_constructible<T, U&&>::value )
{
return m_has_value ?
std::move( m_value ) : static_cast<T>( std::forward<U>( default_value ) );
}
private:
template <typename U>
auto set_value( U&& value )
noexcept( std::is_nothrow_constructible<T, decltype( value )>::value )
-> std::enable_if_t<std::is_constructible<T, decltype( value )>::value>
{
if ( m_has_value )
{
m_value = std::forward<U>( value );
}
else
{
m_has_value = true;
emplace( std::forward<U>( value ) );
}
}
constexpr void destroy_value()
noexcept( std::is_nothrow_destructible<T>::value )
{
if ( !std::is_trivially_destructible<T>::value && m_has_value )
{
m_value.T::~T();
}
}
bool m_has_value;
union
{
T m_value;
};
};
// 20.5.10, specialized algorithms - swap
template <typename T>
void swap( optional<T>& lhs, optional<T>& rhs )
noexcept( noexcept( lhs.swap( rhs ) ) )
{
lhs.swap( rhs );
}
// 20.5.10, specialized algorithms - make_optional
template <typename T>
optional<std::decay_t<T>> make_optional( T&& value )
noexcept( std::is_nothrow_constructible<T, decltype( value )>::value )
{
return optional<std::decay_t<T>>{ std::forward<T>( value ) };
}
}
namespace std
{
// 20.5.11, hash support
template <typename T>
struct hash<util::optional<T>>
{
auto operator()( util::optional<T> const& value ) const
{
static hash<T> hasher;
return hasher( *value );
}
};
}
#endif
optional_comparison.h
#ifndef UTIL_OPTIONAL_COMPARISON_H
#define UTIL_OPTIONAL_COMPARISON_H
namespace util
{
// 20.5.5, no-value state indicator
struct nullopt_t;
// 20.5.3, optional for object types
template <typename T> class optional;
// 20.5.7, relational operators
template <typename T>
constexpr bool operator==( optional<T> const& lhs, optional<T> const& rhs )
noexcept( noexcept( *lhs == *rhs ) )
{
/* function code is equivalent to:
if ( static_cast<bool>( lhs ) != static_cast<bool>( rhs ) )
return false;
else if ( !lhs )
return true;
else
return *lhs == *rhs;
*/
// vc++2015 update 2 constexpr compliant
return static_cast<bool>( lhs ) != static_cast<bool>( rhs ) ?
false : !lhs ? true : *lhs == *rhs;
}
template <typename T>
constexpr bool operator!=( optional<T> const& lhs, optional<T> const& rhs )
noexcept( noexcept( operator==( lhs, rhs ) ) )
{
return !operator==( lhs, rhs );
}
template <typename T>
constexpr bool operator<( optional<T> const& lhs, optional<T> const& rhs )
noexcept( noexcept( *lhs < *rhs ) )
{
/* function code is equivalent to:
if ( !rhs )
return false;
else if ( !lhs )
return true;
else
return *lhs < *rhs;
*/
// vc++2015 update 2 constexpr compliant
return !rhs ? false : !lhs ? true : *lhs < *rhs;
}
template <typename T>
constexpr bool operator>( optional<T> const& lhs, optional<T> const& rhs )
noexcept( noexcept( operator<( lhs, rhs ) ) )
{
return operator<( rhs, lhs );
}
template <typename T>
constexpr bool operator<=( optional<T> const& lhs, optional<T> const& rhs )
noexcept( noexcept( operator>( lhs, rhs ) ) )
{
return !operator<( rhs, lhs );
}
template <typename T>
constexpr bool operator>=( optional<T> const& lhs, optional<T> const& rhs )
noexcept( noexcept( operator<( lhs, rhs ) ) )
{
return !operator<( lhs, rhs );
}
// 20.5.8, comparison with nullopt
template <typename T>
constexpr bool operator==( optional<T> const& opt, nullopt_t ) noexcept
{
return !opt;
}
template <typename T>
constexpr bool operator==( nullopt_t, optional<T> const& opt ) noexcept
{
return !opt;
}
template <typename T>
constexpr bool operator!=( optional<T> const& opt, nullopt_t ) noexcept
{
return static_cast<bool>( opt );
}
template <typename T>
constexpr bool operator!=( nullopt_t, optional<T> const& opt ) noexcept
{
return static_cast<bool>( opt );
}
template <typename T>
constexpr bool operator<( optional<T> const& opt, nullopt_t ) noexcept
{
return false;
}
template <typename T>
constexpr bool operator<( nullopt_t, optional<T> const& opt ) noexcept
{
return static_cast<bool>( opt );
}
template <typename T>
constexpr bool operator<=( optional<T> const& opt, nullopt_t ) noexcept
{
return !opt;
}
template <typename T>
constexpr bool operator<=( nullopt_t, optional<T> const& opt ) noexcept
{
return true;
}
template <typename T>
constexpr bool operator>( optional<T> const& opt, nullopt_t ) noexcept
{
return static_cast<bool>( opt );
}
template <typename T>
constexpr bool operator>( nullopt_t, optional<T> const& opt ) noexcept
{
return false;
}
template <typename T>
constexpr bool operator>=( optional<T> const& opt, nullopt_t ) noexcept
{
return true;
}
template <typename T>
constexpr bool operator>=( nullopt_t, optional<T> const& opt ) noexcept
{
return !opt;
}
// 20.5.9, comparison with T
template <typename T>
constexpr bool operator==( optional<T> const& opt, T const& value )
noexcept( noexcept( *opt == value ) )
{
return opt ? *opt == value : false;
}
template <typename T>
constexpr bool operator==( T const& value, optional<T> const& opt )
noexcept( noexcept( operator==( opt, value ) ) )
{
return operator==( opt, value );
}
template <typename T>
constexpr bool operator!=( optional<T> const& opt, T const& value )
noexcept( noexcept( operator==( opt, value ) ) )
{
return !operator==( opt, value );
}
template <typename T>
constexpr bool operator!=( T const& value, optional<T> const& opt )
noexcept( noexcept( operator==( opt, value ) ) )
{
return !operator==( opt, value );
}
template <typename T>
constexpr bool operator<( optional<T> const& opt, T const& value )
noexcept( noexcept( *opt < value ) )
{
return opt ? *opt < value : true;
}
template <typename T>
constexpr bool operator<( T const& value, optional<T> const& opt )
noexcept( noexcept( operator<( opt, value ) ) )
{
return opt ? value < *opt : false;
}
template <typename T>
constexpr bool operator<=( optional<T> const& opt, T const& value )
noexcept( noexcept( operator>( opt, value ) ) )
{
return !operator>( opt, value );
}
template <typename T>
constexpr bool operator<=( T const& value, optional<T> const& opt )
noexcept( noexcept( operator>( value, opt ) ) )
{
return !operator>( value, opt );
}
template <typename T>
constexpr bool operator>( optional<T> const& opt, T const& value )
noexcept( noexcept( operator<( value, opt ) ) )
{
return opt ? value < *opt : false;
}
template <typename T>
constexpr bool operator>( T const& value, optional<T> const& opt )
noexcept( noexcept( operator<( opt, value ) ) )
{
return opt ? *opt < value : true;
}
template <typename T>
constexpr bool operator>=( optional<T> const& opt, T const& value )
noexcept( noexcept( operator<( opt, value ) ) )
{
return !operator<( opt, value );
}
template <typename T>
constexpr bool operator>=( T const& value, optional<T> const& opt )
noexcept( noexcept( operator<( value, opt ) ) )
{
return !operator<( value, opt );
}
}
#endif