12
\$\begingroup\$

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
\$\endgroup\$

2 Answers 2

7
\$\begingroup\$

Bugs

Consider this:

T val;
optional<T> o;
o = val;

This calls operator=(U&& value) with U = T&, which calls set_value(U&& value) with U = T&, which sets m_has_value to true then calls emplace()! This makes emplace() think that you actually have a value, and so destroys it. But we didn't have a value originally - so undefined behavior.

You have similar bugs in other places. emplace() is responsible for setting m_has_value() to true, nothing else.

In a similar vein, destroy_value() should set m_has_value to false, so that you don't have to manually do it in other places.


Constructors

This constructor needs SFINAE:

template <typename... Args>
constexpr optional( in_place_t, Args&&... args )

Otherwise you'll get the wrong thing from is_constructible.

This constructor seems wrong - what types are constructible from both an initializer_list and something else (I'm omitting the SFINAE for brevity)? Perhaps just the initializer_list?

template<typename U, typename... Args>
constexpr optional( in_place_t, std::initializer_list<U> il, Args&&... args )

Assignment

This cast is unnecessary:

if ( static_cast<void*>( this ) != &other )

Destructor

You don't have to check if is_trivially_destructible<T>::value. If it were trivially destructible, the destructor just wouldn't do anything. So you can just write:

constexpr void destroy_value() noexcept(std::is_nothrow_destructible<T>::value)
{
    if (m_has_value) {
        m_value.T::~T();
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ Bugs: Thanks for pointing these out. Constructors: (1) The standard does not say that the first in_place_t constructor should be removed from overload resolution. If that's not what you meant, please tell clarify. (2) This constructor is required by the standard; example use: optional<vector<int>> ov{ in_place, { 1, 2, 3 }, myalloc<int>{} }. \$\endgroup\$ Commented May 4, 2016 at 23:28
  • \$\begingroup\$ But yes, that constructor is weird; it's the first of its kind that I've seen. \$\endgroup\$ Commented May 4, 2016 at 23:39
2
\$\begingroup\$

20.5.3.2, destructor - making it trivial

The standard indicates that if std::is_trivially_destructible<T>::value == true, then optional<T> must be trivially destructible.

In order to achieve this, the uninitialized storage cannot be in an union, as that causes optional<T>'s destructor be declared as deleted.

Here's the type to do just that:

#ifndef UTIL_MAYBE_TRIVIALLY_DESTRUCTIBLE_STORAGE_H
#define UTIL_MAYBE_TRIVIALLY_DESTRUCTIBLE_STORAGE_H

#include <new>
#include <type_traits>
#include <utility>

namespace util
{
    template <typename T>
    struct maybe_trivially_destructible_storage_base
    {
        explicit maybe_trivially_destructible_storage_base( bool const has_value )
            : m_has_value{ has_value }
        {}

        template
        <
            typename... Args,
            std::enable_if_t<std::is_constructible<T, Args&&...>::value, int> = 0
        >
        maybe_trivially_destructible_storage_base( Args&&... args )
            : m_has_value{ true }
        {
            ::new ( &m_data ) T( std::forward<Args>( args )... );
        }

        //  issue : vc++2015 update 2 cannot declare the following as constexpr
        //      T& value() & noexcept;
        //      T const& value() const & noexcept;
        constexpr T& value() & noexcept
        {
            return reinterpret_cast<T&>( m_data );
        }

        constexpr T const& value() const & noexcept
        {
            return reinterpret_cast<T const&>( m_data );
        }

        bool m_has_value;
        std::aligned_storage_t<sizeof( T ), alignof( T )> m_data;
    };

    template
    <
        typename T,
        bool is_trivially_destructible = std::is_trivially_destructible<T>::value
    >
    struct maybe_trivially_destructible_storage
        : public maybe_trivially_destructible_storage_base<T>
    {
        maybe_trivially_destructible_storage( bool const has_value )
            : maybe_trivially_destructible_storage_base<T>( has_value )
        {}

        template
        <
            typename... Args,
            std::enable_if_t<std::is_constructible<T, Args&&...>::value, int> = 0
        >
        maybe_trivially_destructible_storage( Args&&... args )
            : maybe_trivially_destructible_storage_base<T>( std::forward<Args>( args )... )
        {}
    };

    template <typename T>
    struct maybe_trivially_destructible_storage<T, false>
        : public maybe_trivially_destructible_storage_base<T>
    {
        maybe_trivially_destructible_storage( bool const has_value )
            : maybe_trivially_destructible_storage_base<T>( has_value )
        {}

        template
        <
            typename... Args,
            std::enable_if_t<std::is_constructible<T, Args&&...>::value, int> = 0
        >
        maybe_trivially_destructible_storage( Args&&... args )
            : maybe_trivially_destructible_storage_base<T>( std::forward<Args>( args )... )
        {}

        ~maybe_trivially_destructible_storage()
            noexcept( std::is_nothrow_destructible<T>::value )
        {
            if ( maybe_trivially_destructible_storage_base<T>::m_has_value )
            {
                maybe_trivially_destructible_storage_base<T>::value().T::~T();
            }
        };
    };
}
#endif

By using this new type, optional<T> is now trivially destructible if T is trivially destructible.

Using the new storage type and bug fixes

This is the full implementation of optional<T> using the new storage type and taking into account what Barry's answer points out. The comparison operators stay the same.

#ifndef UTIL_OPTIONAL_H
#define UTIL_OPTIONAL_H

#include <new>
#include <stdexcept>
#include <type_traits>
#include "maybe_trivially_destructible_storage.h"
#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
    {
    public:
        template <typename U> friend class optional;

        using value_type = T;

        // 20.5.3.2, destructor
        ~optional() = default;

        // 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_storage{ false } {}
        constexpr optional( nullopt_t ) noexcept : m_storage{ false } {}

        constexpr optional( optional const& other )
            noexcept( std::is_nothrow_copy_constructible<T>::value )
            : m_storage{ other.m_storage.m_has_value }
        {
            if ( m_storage.m_has_value )
            {
                emplace( other.m_storage.value() );
            }
        }

        constexpr optional( optional&& other )
            noexcept( std::is_nothrow_move_constructible<T>::value )
            : m_storage( other.m_storage.m_has_value )
        {
            if ( m_storage.m_has_value )
            {
                emplace( std::move( other.m_storage.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_storage( 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_storage( il, std::forward<Args>( args )... )
        {}

        // 20.5.3.3, assignment
        optional& operator=( nullopt_t )
            noexcept( std::is_nothrow_destructible<T>::value )
        {
            destroy_value();
            return *this;
        }

        optional& operator=( optional const& other )
            noexcept( std::is_nothrow_copy_constructible<T>::value &&
                std::is_nothrow_copy_assignable<T>::value )
        {
            if ( this != &other )
            {
                if ( other.m_storage.m_has_value )
                {
                    set_value( other.m_storage.value() );
                }
                else
                {
                    destroy_value();
                }
            }
            return *this;
        }

        optional& operator=( optional&& other )
            noexcept( std::is_nothrow_move_constructible<T>::value &&
                std::is_nothrow_move_assignable<T>::value )
        {
            if ( this != &other )
            {
                if ( other.m_storage.m_has_value )
                {
                    set_value( std::move( other.m_storage.value() ) );
                }
                else
                {
                    destroy_value();
                }
            }
            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_storage.m_data ) T( std::forward<Args>( args )... );
            m_storage.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_storage.m_data ) T( il, std::forward<Args>( args )... );
            m_storage.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_storage.m_has_value && other.m_storage.m_has_value )
                {
                    using std::swap;
                    swap( m_storage.value(), other.m_storage.value() );
                }
                else if ( m_storage.m_has_value )
                {
                    other.emplace( std::move( m_storage.value() ) );
                    destroy_value();
                }
                else if ( other.m_storage.m_has_value )
                {
                    emplace( std::move( other.m_storage.value() ) );
                    other.destroy_value();
                }
            }
        }

        // 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_storage.value(); }
        constexpr T const* operator->() const noexcept
        { return &m_storage.value(); }

        constexpr T& operator*() & noexcept { return m_storage.value(); }
        constexpr T const& operator*() const & noexcept
        { return m_storage.value(); }

        constexpr T&& operator*() && noexcept { return m_storage.value(); }
        constexpr T const&& operator*() const && noexcept
        { return m_storage.value(); }

        constexpr explicit operator bool() const noexcept
        { return m_storage.m_has_value; }

        constexpr T& value() &
        {
            if ( m_storage.m_has_value ) return m_storage.value();
            throw bad_optional_access{};
        }

        constexpr T const& value() const&
        {
            if ( m_storage.m_has_value ) return m_storage.value();
            throw bad_optional_access{};
        }

        constexpr T&& value() &&
        {
            if ( m_storage.m_has_value ) return std::move( m_storage.value() );
            throw bad_optional_access{};
        }

        constexpr T const&& value() const&&
        {
            if ( m_storage.m_has_value ) return std::move( m_storage.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_storage.m_has_value ?
                m_storage.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_storage.m_has_value ?
                std::move( m_storage.value() ) :
                static_cast<T>( std::forward<U>( default_value ) );
        }

    private:
        template <typename U>
        void set_value( U&& value )
            noexcept( std::is_nothrow_constructible<T, decltype( value )>::value )
        {
            if ( m_storage.m_has_value )
            {
                m_storage.value() = std::forward<U>( value );
            }
            else
            {
                emplace( std::forward<U>( value ) );
            }
        }

        void destroy_value()
            noexcept( std::is_nothrow_destructible<T>::value )
        {
            if ( m_storage.m_has_value )
            {
                m_storage.value().T::~T();
                m_storage.m_has_value = false;
            }
        }

        maybe_trivially_destructible_storage<T> m_storage;
    };

    // 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

Sample usage

With all that out of the way, here's how the class can be used.

#include <iostream>
#include <string>
#include <vector>
#include "optional.h"

int main()
{
    // vc++2015 has an issue with is_trivially_destructible
    static_assert( std::is_trivially_destructible<util::optional<int>>::value, "!" );

    std::string str{ "mystr" };
    util::optional<std::string> s0;

    s0 = str;
    std::cout << *s0 << '\n';

    s0 = util::nullopt;
    std::cout << s0.value_or( "no value, here's a default" ) << '\n';
    try
    {
        s0.value();
    }
    catch ( util::bad_optional_access const& e )
    {
        std::cout << e.what() << '\n';
    }

    util::optional<std::vector<int>> s8
    {
        util::in_place, // constructed in place
        { 1, 2, 3 }, // initializer list
        std::allocator<int>{} // allocator
    };
    for ( auto i : *s8 )
        std::cout << i << '\n';
}
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.