diff --git a/dlib/optional.h b/dlib/optional.h new file mode 100644 index 000000000..17e8742fb --- /dev/null +++ b/dlib/optional.h @@ -0,0 +1,1017 @@ +// Copyright (C) 2023 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_OPTIONAL_H +#define DLIB_OPTIONAL_H + +#include +#include +#include "functional.h" + +namespace dlib +{ + +// --------------------------------------------------------------------------------------------------- + + class bad_optional_access : public std::exception + { + public: + bad_optional_access() = default; + const char *what() const noexcept { return "Optional has no value"; } + }; + +// --------------------------------------------------------------------------------------------------- + + struct nullopt_t + { + nullopt_t() = delete; + constexpr explicit nullopt_t(int) noexcept {} + }; + + static constexpr nullopt_t nullopt{int{}}; + +// --------------------------------------------------------------------------------------------------- + + template + class optional; + +// --------------------------------------------------------------------------------------------------- + + namespace details + { + template + struct is_optional : std::false_type{}; + + template + struct is_optional> : std::true_type{}; + + template + using is_constructible_from = And< + !std::is_constructible&>::value, + !std::is_constructible&>::value, + !std::is_constructible&&>::value, + !std::is_constructible&&>::value, + !std::is_convertible< dlib::optional&, T>::value, + !std::is_convertible&, T>::value, + !std::is_convertible< dlib::optional&&, T>::value, + !std::is_convertible&&, T>::value + >; + + template + using is_assignable_from = And< + is_constructible_from::value, + std::is_assignable&>::value, + std::is_assignable&>::value, + std::is_assignable&&>::value, + std::is_assignable&&>::value + >; + + template + using is_copy_convertible = std::enable_if_t< + is_constructible_from::value && + std::is_constructible::value, + bool + >; + + template + using is_move_convertible = std::enable_if_t< + is_constructible_from::value && + std::is_constructible::value, + bool + >; + + template + using is_copy_assignable = std::enable_if_t< + is_assignable_from::value && + std::is_constructible::value && + std::is_assignable::value, + bool + >; + + template + using is_move_assignable = std::enable_if_t< + is_assignable_from::value && + std::is_constructible::value && + std::is_assignable::value, + bool + >; + + template> + using is_construct_convertible_from = std::enable_if_t< + std::is_constructible::value && + !std::is_same::value && + !std::is_same>::value, + bool + >; + + template > + using is_assign_convertible_from = std::enable_if_t< + std::is_constructible::value && + std::is_assignable::value && + !std::is_same>::value && + (!std::is_scalar::value || !std::is_same::value), + bool + >; + +// --------------------------------------------------------------------------------------------------- + + template < + class T, + bool = std::is_trivially_destructible::value + > + struct optional_storage + { + constexpr optional_storage() noexcept + : e{}, active{false} + {} + + template + constexpr optional_storage(in_place_t, U&& ...u) noexcept(std::is_nothrow_constructible::value) + : val{std::forward(u)...}, active{true} + {} + + ~optional_storage() noexcept(std::is_nothrow_destructible::value) + { + if (active) + val.~T(); + } + + struct empty{}; + union {T val; empty e;}; + bool active{false}; + }; + + template + struct optional_storage + { + constexpr optional_storage() noexcept + : e{}, active{false} + {} + + template + constexpr optional_storage(in_place_t, U&& ...u) noexcept(std::is_nothrow_constructible::value) + : val{std::forward(u)...}, active{true} + {} + + struct empty{}; + union {T val; empty e;}; + bool active{false}; + }; + +// --------------------------------------------------------------------------------------------------- + + template + struct optional_ops : optional_storage + { + using optional_storage::optional_storage; + + template + constexpr void construct(U&&... u) noexcept(std::is_nothrow_constructible::value) + { + new (std::addressof(this->val)) T(std::forward(u)...); + this->active = true; + } + + template + constexpr void assign(Optional&& rhs) noexcept(std::is_nothrow_constructible::value && + std::is_nothrow_assignable::value) + { + if (this->active && rhs.active) + this->val = std::forward(rhs).val; + else if (!this->active && rhs.active) + construct(std::forward(rhs).val); + else if (this->active && !rhs.active) + destruct(); + } + + constexpr void destruct() noexcept(std::is_nothrow_destructible::value) + { + if (this->active) + { + this->val.~T(); + this->active = false; + } + } + }; + +// --------------------------------------------------------------------------------------------------- + + template ::value> + struct optional_copy : optional_ops + { + using optional_ops::optional_ops; + }; + + template + struct optional_copy : optional_ops + { + using optional_ops::optional_ops; + + constexpr optional_copy() = default; + constexpr optional_copy(optional_copy&& rhs) = default; + constexpr optional_copy &operator=(const optional_copy& rhs) = default; + constexpr optional_copy &operator=(optional_copy&& rhs) = default; + + constexpr optional_copy(const optional_copy& rhs) noexcept(std::is_nothrow_copy_constructible::value) + : optional_ops() + { + if (rhs.active) + this->construct(rhs.val); + } + }; + +// --------------------------------------------------------------------------------------------------- + + template ::value> + struct optional_move : optional_copy + { + using optional_copy::optional_copy; + }; + + template + struct optional_move : optional_copy + { + using optional_copy::optional_copy; + + constexpr optional_move() = default; + constexpr optional_move(const optional_move& rhs) = default; + constexpr optional_move& operator=(const optional_move& rhs) = default; + constexpr optional_move& operator=(optional_move&& rhs) = default; + + constexpr optional_move(optional_move&& rhs) noexcept(std::is_nothrow_move_constructible::value) + { + if (rhs.active) + this->construct(std::move(rhs.val)); + } + }; + +// --------------------------------------------------------------------------------------------------- + + template < + class T, + bool = std::is_trivially_copy_assignable::value && + std::is_trivially_copy_constructible::value && + std::is_trivially_destructible::value + > + struct optional_copy_assign : optional_move + { + using optional_move::optional_move; + }; + + template + struct optional_copy_assign : optional_move + { + using optional_move::optional_move; + + constexpr optional_copy_assign() = default; + constexpr optional_copy_assign(const optional_copy_assign& rhs) = default; + constexpr optional_copy_assign(optional_copy_assign&& rhs) = default; + constexpr optional_copy_assign& operator=(optional_copy_assign &&rhs) = default; + + constexpr optional_copy_assign& operator=(const optional_copy_assign &rhs) + noexcept(std::is_nothrow_copy_constructible::value && + std::is_nothrow_copy_assignable::value) + { + this->assign(rhs); + return *this; + } + }; + +// --------------------------------------------------------------------------------------------------- + + template < + class T, + bool = std::is_trivially_destructible::value && + std::is_trivially_move_constructible::value && + std::is_trivially_move_assignable::value + > + struct optional_move_assign : optional_copy_assign + { + using optional_copy_assign::optional_copy_assign; + }; + + template + struct optional_move_assign : optional_copy_assign + { + using optional_copy_assign::optional_copy_assign; + + constexpr optional_move_assign() = default; + constexpr optional_move_assign(const optional_move_assign &rhs) = default; + constexpr optional_move_assign(optional_move_assign &&rhs) = default; + constexpr optional_move_assign& operator=(const optional_move_assign &rhs) = default; + + constexpr optional_move_assign& operator=(optional_move_assign &&rhs) + noexcept(std::is_nothrow_move_constructible::value && + std::is_nothrow_move_assignable::value) + { + this->assign(std::move(rhs)); + return *this; + } + }; + +// --------------------------------------------------------------------------------------------------- + + template < + class T, + bool copyable = std::is_copy_constructible::value, + bool moveable = std::is_move_constructible::value + > + struct optional_delete_constructors + { + constexpr optional_delete_constructors() = default; + constexpr optional_delete_constructors(const optional_delete_constructors&) = default; + constexpr optional_delete_constructors(optional_delete_constructors&&) = default; + constexpr optional_delete_constructors& operator=(const optional_delete_constructors &) = default; + constexpr optional_delete_constructors& operator=(optional_delete_constructors &&) = default; + }; + + template + struct optional_delete_constructors + { + constexpr optional_delete_constructors() = default; + constexpr optional_delete_constructors(const optional_delete_constructors&) = default; + constexpr optional_delete_constructors(optional_delete_constructors&&) = delete; + constexpr optional_delete_constructors& operator=(const optional_delete_constructors &) = default; + constexpr optional_delete_constructors& operator=(optional_delete_constructors &&) = default; + }; + + template + struct optional_delete_constructors + { + constexpr optional_delete_constructors() = default; + constexpr optional_delete_constructors(const optional_delete_constructors&) = delete; + constexpr optional_delete_constructors(optional_delete_constructors&&) = default; + constexpr optional_delete_constructors& operator=(const optional_delete_constructors &) = default; + constexpr optional_delete_constructors& operator=(optional_delete_constructors &&) = default; + }; + + template + struct optional_delete_constructors + { + constexpr optional_delete_constructors() = default; + constexpr optional_delete_constructors(const optional_delete_constructors&) = delete; + constexpr optional_delete_constructors(optional_delete_constructors&&) = delete; + constexpr optional_delete_constructors& operator=(const optional_delete_constructors&) = default; + constexpr optional_delete_constructors& operator=(optional_delete_constructors &&) = default; + }; + +// --------------------------------------------------------------------------------------------------- + + template < + class T, + bool copyable = (std::is_copy_constructible::value && std::is_copy_assignable::value), + bool moveable = (std::is_move_constructible::value && std::is_move_assignable::value) + > + struct optional_delete_assign + { + constexpr optional_delete_assign() = default; + constexpr optional_delete_assign(const optional_delete_assign &) = default; + constexpr optional_delete_assign(optional_delete_assign &&) = default; + constexpr optional_delete_assign& operator=(const optional_delete_assign &) = default; + constexpr optional_delete_assign& operator=(optional_delete_assign &&) = default; + }; + + template + struct optional_delete_assign + { + constexpr optional_delete_assign() = default; + constexpr optional_delete_assign(const optional_delete_assign &) = default; + constexpr optional_delete_assign(optional_delete_assign &&) = default; + constexpr optional_delete_assign& operator=(const optional_delete_assign &) = default; + constexpr optional_delete_assign& operator=(optional_delete_assign &&) = delete; + }; + + template + struct optional_delete_assign + { + constexpr optional_delete_assign() = default; + constexpr optional_delete_assign(const optional_delete_assign &) = default; + constexpr optional_delete_assign(optional_delete_assign &&) = default; + constexpr optional_delete_assign& operator=(const optional_delete_assign &) = delete; + constexpr optional_delete_assign& operator=(optional_delete_assign &&) = default; + }; + + template + struct optional_delete_assign + { + constexpr optional_delete_assign() = default; + constexpr optional_delete_assign(const optional_delete_assign &) = default; + constexpr optional_delete_assign(optional_delete_assign &&) = default; + constexpr optional_delete_assign& operator=(const optional_delete_assign &) = delete; + constexpr optional_delete_assign& operator=(optional_delete_assign &&) = delete; + }; + +// --------------------------------------------------------------------------------------------------- + + } + +// --------------------------------------------------------------------------------------------------- + + template + class optional : private details::optional_move_assign, + private details::optional_delete_constructors, + private details::optional_delete_assign + { + /*! + WHAT THIS OBJECT REPRESENTS + This is a standard's compliant backport of std::optional that works with C++14. + It includes C++23 monadic interfaces + !*/ + + using base = details::optional_move_assign; + + static_assert(!std::is_reference::value, "optional not allowed"); + static_assert(!std::is_same::value, "optional not allowed"); + static_assert(!std::is_same::value, "optional not allowed"); + + public: + using value_type = T; + + constexpr optional() = default; + constexpr optional(const optional &rhs) = default; + constexpr optional(optional &&rhs) = default; + constexpr optional& operator=(const optional &rhs) = default; + constexpr optional& operator=(optional &&rhs) = default; + ~optional() = default; + + constexpr optional(nullopt_t) noexcept {} + + constexpr optional& operator=(nullopt_t) noexcept + { + if (*this) + reset(); + return *this; + } + + template < + class U, + details::is_copy_convertible = true, + std::enable_if_t::value, bool> = true + > + constexpr explicit optional(const optional &rhs) noexcept(std::is_nothrow_constructible::value) + { + if (rhs) + this->construct(*rhs); + } + + template < + class U, + details::is_copy_convertible = true, + std::enable_if_t::value, bool> = true + > + constexpr optional(const optional &rhs) noexcept(std::is_nothrow_constructible::value) + { + if (rhs) + this->construct(*rhs); + } + + template < + class U, + details::is_move_convertible = true, + std::enable_if_t::value, bool> = true + > + constexpr explicit optional(optional&& rhs) noexcept(std::is_nothrow_constructible::value) + { + if (rhs) + this->construct(std::move(*rhs)); + } + + template < + class U, + details::is_move_convertible = true, + std::enable_if_t::value, bool> = true + > + constexpr optional(optional&& rhs) noexcept(std::is_nothrow_constructible::value) + { + if (rhs) + this->construct(std::move(*rhs)); + } + + template < + class... Args, + std::enable_if_t::value, bool> = true + > + constexpr explicit optional ( + in_place_t, + Args&&... args + ) noexcept(std::is_nothrow_constructible::value) + : base(in_place, std::forward(args)...) + { + } + + template < + class U, + class... Args, + std::enable_if_t&, Args&&...>::value, bool> = true + > + constexpr explicit optional ( + in_place_t, + std::initializer_list il, + Args &&... args + ) noexcept(std::is_nothrow_constructible&,Args&&...>::value) + { + this->construct(il, std::forward(args)...); + } + + template< + class U, + details::is_construct_convertible_from = true, + std::enable_if_t::value, bool> = true + > + constexpr explicit optional(U &&u) noexcept(std::is_nothrow_constructible::value) + : base(in_place, std::forward(u)) + { + } + + template< + class U, + details::is_construct_convertible_from = true, + std::enable_if_t::value, bool> = true + > + constexpr optional(U &&u) noexcept(std::is_nothrow_constructible::value) + : base(in_place, std::forward(u)) + { + } + + template < + class U, + details::is_copy_assignable = true + > + constexpr optional &operator=(const optional& rhs) noexcept(std::is_nothrow_constructible::value && + std::is_nothrow_assignable::value) + { + this->assign(rhs); + return *this; + } + + template < + class U, + details::is_move_assignable = true + > + constexpr optional &operator=(optional&& rhs) noexcept(std::is_nothrow_constructible::value && + std::is_nothrow_assignable::value) + { + this->assign(std::move(rhs)); + return *this; + } + + template < + class U, + details::is_assign_convertible_from = true + > + constexpr optional& operator=(U &&u) noexcept(std::is_nothrow_constructible::value && + std::is_nothrow_assignable::value) + { + if (*this) + **this = std::forward(u); + else + this->construct(std::forward(u)); + return *this; + } + + template + constexpr T& emplace(Args &&... args) noexcept(std::is_nothrow_constructible::value) + { + reset(); + this->construct(std::forward(args)...); + return **this; + } + + template + constexpr T& emplace(std::initializer_list il, Args &&... args) + { + reset(); + this->construct(il, std::forward(args)...); + return **this; + } + + void swap(optional& rhs) noexcept(std::is_nothrow_move_constructible::value && + dlib::is_nothrow_swappable::value) + { + using std::swap; + + if (*this && rhs) + { + swap(**this, *rhs); + } + + else if (*this && !rhs) + { + rhs = std::move(**this); + reset(); + } + + else if (!*this && rhs) + { + *this = std::move(*rhs); + rhs.reset(); + } + } + + constexpr const T* operator->() const noexcept { return &this->val; } + constexpr T* operator->() noexcept { return &this->val; } + constexpr T& operator*() & noexcept { return this->val; } + constexpr const T& operator*() const& noexcept { return this->val; } + constexpr T&& operator*() && noexcept { return std::move(this->val); } + constexpr const T&& operator*() const&& noexcept { return std::move(this->val); } + constexpr explicit operator bool() const noexcept { return this->active; } + constexpr bool has_value() const noexcept { return this->active; } + + constexpr T& value() & + { + if (*this) + return **this; + throw bad_optional_access(); + } + + constexpr const T& value() const & + { + if (*this) + return **this; + throw bad_optional_access(); + } + + constexpr T&& value() && + { + if (*this) + return std::move(**this); + throw bad_optional_access(); + } + + constexpr const T&& value() const && + { + if (*this) + return std::move(**this); + throw bad_optional_access(); + } + + template + constexpr T value_or(U &&u) const & + { + return *this ? **this : static_cast(std::forward(u)); + } + + template + constexpr T value_or(U &&u) && + { + return *this ? std::move(**this) : static_cast(std::forward(u)); + } + + void reset() noexcept(std::is_nothrow_destructible::value) + { + this->destruct(); + } + + template < + class F, + class Return = dlib::remove_cvref_t>, + std::enable_if_t::value, bool> = true + > + constexpr auto and_then(F&& f) & + { + if (*this) + return dlib::invoke(std::forward(f), **this); + else + return Return{}; + } + + template < + class F, + class Return = dlib::remove_cvref_t>, + std::enable_if_t::value, bool> = true + > + constexpr auto and_then(F&& f) const& + { + if (*this) + return dlib::invoke(std::forward(f), **this); + else + return Return{}; + } + + template < + class F, + class Return = dlib::remove_cvref_t>, + std::enable_if_t::value, bool> = true + > + constexpr auto and_then(F&& f) && + { + if (*this) + return dlib::invoke(std::forward(f), std::move(**this)); + else + return Return{}; + } + + template < + class F, + class Return = dlib::remove_cvref_t>, + std::enable_if_t::value, bool> = true + > + constexpr auto and_then(F&& f) const&& + { + if (*this) + return dlib::invoke(std::forward(f), std::move(**this)); + else + return Return{}; + } + + template < + class F, + class U = dlib::remove_cvref_t>, + std::enable_if_t::value, bool> = true, + std::enable_if_t::value, bool> = true + > + constexpr dlib::optional transform(F&& f) & + { + if (*this) + return dlib::invoke(std::forward(f), **this); + else + return dlib::optional{}; + } + + template < + class F, + class U = dlib::remove_cvref_t>, + std::enable_if_t::value, bool> = true, + std::enable_if_t::value, bool> = true + > + constexpr dlib::optional transform(F&& f) const& + { + if (*this) + return dlib::invoke(std::forward(f), **this); + else + return dlib::optional{}; + } + + template < + class F, + class U = dlib::remove_cvref_t>, + std::enable_if_t::value, bool> = true, + std::enable_if_t::value, bool> = true + > + constexpr dlib::optional transform(F&& f) && + { + if (*this) + return dlib::invoke(std::forward(f), std::move(**this)); + else + return dlib::optional{}; + } + + template < + class F, + class U = dlib::remove_cvref_t>, + std::enable_if_t::value, bool> = true, + std::enable_if_t::value, bool> = true + > + constexpr dlib::optional transform(F&& f) const&& + { + if (*this) + return dlib::invoke(std::forward(f), std::move(**this)); + else + return dlib::optional{}; + } + + template < + class F, + class U = dlib::remove_cvref_t>, + std::enable_if_t>::value, bool> = true + > + constexpr optional or_else( F&& f ) const& + { + return *this ? *this : std::forward(f)(); + } + + template < + class F, + class U = dlib::remove_cvref_t>, + std::enable_if_t>::value, bool> = true + > + constexpr optional or_else( F&& f ) && + { + return *this ? std::move(*this) : std::forward(f)(); + } + }; + +// --------------------------------------------------------------------------------------------------- + + template + constexpr bool operator==(const optional &lhs, const optional &rhs) noexcept(noexcept(std::declval() == std::declval())) + { + return bool(lhs) == bool(rhs) && (!bool(lhs) || *lhs == *rhs); + } + + template + constexpr bool operator!=(const optional &lhs, const optional &rhs) noexcept(noexcept(std::declval() != std::declval())) + { + return !(lhs == rhs); + } + + template + constexpr bool operator<(const optional &lhs, const optional &rhs) noexcept(noexcept(std::declval() < std::declval())) + { + return bool(rhs) && (!bool(lhs) || *lhs < *rhs); + } + + template + constexpr bool operator>=(const optional &lhs, const optional &rhs) noexcept(noexcept(std::declval() >= std::declval())) + { + return !(lhs < rhs); + } + + template + constexpr bool operator>(const optional &lhs, const optional &rhs) noexcept(noexcept(std::declval() > std::declval())) + { + return bool(lhs) && (!bool(rhs) || *lhs > *rhs); + } + + template + constexpr bool operator<=(const optional &lhs, const optional &rhs) noexcept(noexcept(std::declval() <= std::declval())) + { + return !(lhs > rhs); + } + +// --------------------------------------------------------------------------------------------------- + + template + constexpr bool operator==(const optional &lhs, nullopt_t) noexcept + { + return !bool(lhs); + } + + template + constexpr bool operator==(nullopt_t, const optional &rhs) noexcept + { + return !bool(rhs); + } + + template + constexpr bool operator!=(const optional &lhs, nullopt_t) noexcept + { + return bool(lhs); + } + + template + constexpr bool operator!=(nullopt_t, const optional &rhs) noexcept + { + return bool(rhs); + } + + template + constexpr bool operator<(const optional &, nullopt_t) noexcept + { + return false; + } + + template + constexpr bool operator<(nullopt_t, const optional &rhs) noexcept + { + return bool(rhs); + } + + template + constexpr bool operator<=(const optional &lhs, nullopt_t) noexcept + { + return !bool(lhs); + } + + template + constexpr bool operator<=(nullopt_t, const optional &) noexcept + { + return true; + } + + template + constexpr bool operator>(const optional &lhs, nullopt_t) noexcept + { + return bool(lhs); + } + + template + constexpr bool operator>(nullopt_t, const optional &) noexcept + { + return false; + } + + template + constexpr bool operator>=(const optional &, nullopt_t) noexcept + { + return true; + } + + template + constexpr bool operator>=(nullopt_t, const optional &rhs) noexcept + { + return !rhs.has_value(); + } + +// --------------------------------------------------------------------------------------------------- + + template + constexpr bool operator==(const optional &lhs, const U &rhs) + { + return bool(lhs) ? *lhs == rhs : false; + } + + template + constexpr bool operator==(const U &lhs, const optional &rhs) + { + return bool(rhs) ? lhs == *rhs : false; + } + + template + constexpr bool operator!=(const optional &lhs, const U &rhs) + { + return bool(lhs) ? *lhs != rhs : true; + } + + template + constexpr bool operator!=(const U &lhs, const optional &rhs) + { + return bool(rhs) ? lhs != *rhs : true; + } + + template + constexpr bool operator<(const optional &lhs, const U &rhs) + { + return bool(lhs) ? *lhs < rhs : true; + } + + template + constexpr bool operator<(const U &lhs, const optional &rhs) + { + return bool(rhs) ? lhs < *rhs : false; + } + + template + constexpr bool operator<=(const optional &lhs, const U &rhs) + { + return bool(lhs) ? *lhs <= rhs : true; + } + + template + constexpr bool operator<=(const U &lhs, const optional &rhs) + { + return bool(rhs) ? lhs <= *rhs : false; + } + + template + constexpr bool operator>(const optional &lhs, const U &rhs) + { + return bool(lhs) ? *lhs > rhs : false; + } + + template + constexpr bool operator>(const U &lhs, const optional &rhs) + { + return bool(rhs) ? lhs > *rhs : true; + } + + template + constexpr bool operator>=(const optional &lhs, const U &rhs) + { + return bool(lhs) ? *lhs >= rhs : false; + } + + template + constexpr bool operator>=(const U &lhs, const optional &rhs) + { + return bool(rhs) ? lhs >= *rhs : true; + } + +// --------------------------------------------------------------------------------------------------- + + template < + class T, + std::enable_if_t< + std::is_move_constructible::value && + dlib::is_swappable::value, + bool> = true + > + void swap(dlib::optional& lhs, dlib::optional& rhs) noexcept(noexcept(lhs.swap(rhs))) + { + return lhs.swap(rhs); + } + +// --------------------------------------------------------------------------------------------------- + + template< class T > + constexpr dlib::optional> make_optional( T&& value ) + { + return dlib::optional>(std::forward(value)); + } + + template< class T, class... Args > + constexpr dlib::optional make_optional( Args&&... args ) + { + return dlib::optional(dlib::in_place, std::forward(args)...); + } + + template< class T, class U, class... Args > + constexpr dlib::optional make_optional( std::initializer_list il, Args&&... args ) + { + return dlib::optional(dlib::in_place, il, std::forward(args)...); + } + +// --------------------------------------------------------------------------------------------------- + +} + +#endif \ No newline at end of file diff --git a/dlib/test/CMakeLists.txt b/dlib/test/CMakeLists.txt index f99bc1b06..0a17c8247 100644 --- a/dlib/test/CMakeLists.txt +++ b/dlib/test/CMakeLists.txt @@ -161,6 +161,7 @@ add_executable(${target_name} main.cpp tester.cpp elastic_net.cpp te.cpp ffmpeg.cpp + optional.cpp ) get_filename_component(DLIB_FFMPEG_DATA ${CMAKE_SOURCE_DIR}/ffmpeg_data/details.cfg REALPATH) diff --git a/dlib/test/optional.cpp b/dlib/test/optional.cpp new file mode 100644 index 000000000..4f2dbaca3 --- /dev/null +++ b/dlib/test/optional.cpp @@ -0,0 +1,465 @@ +// Copyright (C) 2023 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. + +#include +#include "tester.h" + +namespace +{ + using namespace test; + using namespace dlib; + + logger dlog("test.optional"); + +// --------------------------------------------------------------------------------------------------- + + static_assert(std::is_copy_constructible>::value, "bad"); + static_assert(std::is_copy_assignable>::value, "bad"); + static_assert(std::is_move_constructible>::value, "bad"); + static_assert(std::is_move_assignable>::value, "bad"); + static_assert(std::is_destructible>::value, "bad"); + + static_assert(std::is_trivially_copy_constructible>::value, "bad"); + static_assert(std::is_trivially_copy_assignable>::value, "bad"); + static_assert(std::is_trivially_move_constructible>::value, "bad"); + static_assert(std::is_trivially_move_assignable>::value, "bad"); + static_assert(std::is_trivially_destructible>::value, "bad"); + + static_assert(std::is_nothrow_copy_constructible>::value, "bad"); + static_assert(std::is_nothrow_copy_assignable>::value, "bad"); + static_assert(std::is_nothrow_move_constructible>::value, "bad"); + static_assert(std::is_nothrow_move_assignable>::value, "bad"); + static_assert(std::is_nothrow_destructible>::value, "bad"); + + struct trivial_type + { + trivial_type(const trivial_type&) = default; + trivial_type(trivial_type&&) = default; + trivial_type& operator=(const trivial_type&) = default; + trivial_type& operator=(trivial_type&&) = default; + ~trivial_type() = default; + }; + + static_assert(std::is_copy_constructible>::value, "bad"); + static_assert(std::is_copy_assignable>::value, "bad"); + static_assert(std::is_move_constructible>::value, "bad"); + static_assert(std::is_move_assignable>::value, "bad"); + static_assert(std::is_destructible>::value, "bad"); + + static_assert(std::is_trivially_copy_constructible>::value, "bad"); + static_assert(std::is_trivially_copy_assignable>::value, "bad"); + static_assert(std::is_trivially_move_constructible>::value, "bad"); + static_assert(std::is_trivially_move_assignable>::value, "bad"); + static_assert(std::is_trivially_destructible>::value, "bad"); + + static_assert(std::is_nothrow_copy_constructible>::value, "bad"); + static_assert(std::is_nothrow_copy_assignable>::value, "bad"); + static_assert(std::is_nothrow_move_constructible>::value, "bad"); + static_assert(std::is_nothrow_move_assignable>::value, "bad"); + static_assert(std::is_nothrow_destructible>::value, "bad"); + + struct non_trivial_type + { + non_trivial_type(const non_trivial_type&) {} + non_trivial_type(non_trivial_type&&) {}; + non_trivial_type& operator=(const non_trivial_type&) { return *this; } + non_trivial_type& operator=(non_trivial_type&&) { return *this; }; + ~non_trivial_type() {} + }; + + static_assert(std::is_copy_constructible>::value, "bad"); + static_assert(std::is_copy_assignable>::value, "bad"); + static_assert(std::is_move_constructible>::value, "bad"); + static_assert(std::is_move_assignable>::value, "bad"); + static_assert(std::is_destructible>::value, "bad"); + + static_assert(!std::is_trivially_copy_constructible>::value, "bad"); + static_assert(!std::is_trivially_copy_assignable>::value, "bad"); + static_assert(!std::is_trivially_move_constructible>::value, "bad"); + static_assert(!std::is_trivially_move_assignable>::value, "bad"); + static_assert(!std::is_trivially_destructible>::value, "bad"); + + static_assert(!std::is_nothrow_copy_constructible>::value, "bad"); + static_assert(!std::is_nothrow_copy_assignable>::value, "bad"); + static_assert(!std::is_nothrow_move_constructible>::value, "bad"); + static_assert(!std::is_nothrow_move_assignable>::value, "bad"); + + struct nothing_works + { + nothing_works(const nothing_works&) = delete; + nothing_works(nothing_works&&) = delete; + nothing_works& operator=(const nothing_works&) = delete; + nothing_works& operator=(nothing_works&&) = delete; + }; + + static_assert(!std::is_copy_constructible>::value, "bad"); + static_assert(!std::is_copy_assignable>::value, "bad"); + static_assert(!std::is_move_constructible>::value, "bad"); + static_assert(!std::is_move_assignable>::value, "bad"); + + struct copyable_type + { + copyable_type(const copyable_type&) = default; + copyable_type(copyable_type&&) = delete; + copyable_type& operator=(const copyable_type&) = default; + copyable_type& operator=(copyable_type&&) = delete; + }; + + static_assert(std::is_copy_constructible>::value, "bad"); + static_assert(std::is_copy_assignable>::value, "bad"); + //copyable_type can still be moved, but it will just copy. + + struct moveable_type + { + moveable_type(const moveable_type&) = delete; + moveable_type(moveable_type&&) = default; + moveable_type& operator=(const moveable_type&) = delete; + moveable_type& operator=(moveable_type&&) = default; + }; + + static_assert(!std::is_copy_constructible>::value, "bad"); + static_assert(!std::is_copy_assignable>::value, "bad"); + static_assert(std::is_move_constructible>::value, "bad"); + static_assert(std::is_move_assignable>::value, "bad"); + +// --------------------------------------------------------------------------------------------------- + + void test_optional_int() + { + dlib::optional o1; + DLIB_TEST(!o1); + DLIB_TEST(!o1.has_value()); + DLIB_TEST(o1.value_or(2) == 2); + int throw_counter{0}; + try { + o1.value(); + } catch(const std::exception& e) { + throw_counter++; + } + DLIB_TEST(throw_counter == 1); + DLIB_TEST(o1 == dlib::nullopt); + + dlib::optional o2 = dlib::nullopt; + static_assert(noexcept(o2 = dlib::nullopt), "bad"); + DLIB_TEST(!o2); + DLIB_TEST(!o2.has_value()); + DLIB_TEST(o2.value_or(3) == 3); + + dlib::optional o3 = 42; + DLIB_TEST(*o3 == 42); + DLIB_TEST(o3.value() == 42); + DLIB_TEST(o3.value_or(3) == 42); + DLIB_TEST(o3 == 42); + DLIB_TEST(42 == o3); + + dlib::optional o4 = o3; + static_assert(noexcept(o4 = o3), "bad"); + DLIB_TEST(*o3 == 42); + DLIB_TEST(*o4 == 42); + DLIB_TEST(o4 == 42); + DLIB_TEST(42 == o4); + DLIB_TEST(o4.value() == 42); + + dlib::optional o5 = o1; + static_assert(noexcept(o5 = o1), "bad"); + DLIB_TEST(!o1); + DLIB_TEST(!o5); + DLIB_TEST(!o5.has_value()); + + dlib::optional o6 = std::move(o3); + static_assert(noexcept(o6 = std::move(o3)), "bad"); + DLIB_TEST(*o6 == 42); + DLIB_TEST(o6.value() == 42); + + dlib::optional o7 = (short)42; + static_assert(noexcept(o7 = (short)42), "bad"); + DLIB_TEST(*o7 == 42); + + dlib::optional o8 = o7; + DLIB_TEST(*o7 == 42); + DLIB_TEST(*o8 == 42); + + dlib::optional o9 = std::move(o7); + DLIB_TEST(*o9 == 42); + + dlib::optional o10; + DLIB_TEST(!o10); + o10 = o3; + DLIB_TEST(*o10 == 42); + o10 = dlib::nullopt; + DLIB_TEST(!o10); + + dlib::optional o11; + o11 = std::move(o3); + DLIB_TEST(*o11 == 42); + o11 = (short)12; + DLIB_TEST(*o11 == 12); + + dlib::optional o12; + swap(o12, o4); + DLIB_TEST(o12); + DLIB_TEST(!o4); + DLIB_TEST(*o12 == 42); + static_assert(noexcept(swap(o12, o4)), "bad"); + + o4.reset(); + DLIB_TEST(!o4); + } + +// --------------------------------------------------------------------------------------------------- + + void test_optional_int_constexpr() + { + { + constexpr dlib::optional o2{}; + constexpr dlib::optional o3 = {}; + constexpr dlib::optional o4 = dlib::nullopt; + constexpr dlib::optional o5 = {dlib::nullopt}; + constexpr dlib::optional o6(dlib::nullopt); + + static_assert(!o2, "bad"); + static_assert(!o3, "bad"); + static_assert(!o4, "bad"); + static_assert(!o5, "bad"); + static_assert(!o6, "bad"); + + static_assert(o2.value_or(1) == 1, "bad"); + static_assert(o3.value_or(1) == 1, "bad"); + static_assert(o4.value_or(1) == 1, "bad"); + static_assert(o5.value_or(1) == 1, "bad"); + static_assert(o6.value_or(1) == 1, "bad"); + + static_assert(o2 != 1, "bad"); + static_assert(1 != o2, "bad"); + } + } + +// --------------------------------------------------------------------------------------------------- + + void test_optional_int_monads() + { + dlib::optional o1{42}; + + { + auto res = o1.and_then([](int i) { return dlib::optional(i); }); + + static_assert(std::is_same>::value, "bad map"); + DLIB_TEST(*res == 42); + } + + { + auto res = o1.transform([](int i) { return (long)i; }); + + static_assert(std::is_same>::value, "bad map"); + DLIB_TEST(*res == 42); + } + + { + auto res = o1.and_then([](int i) { return dlib::optional(std::to_string(i)); }); + + static_assert(std::is_same>::value, "bad map"); + DLIB_TEST(*res == "42"); + } + + { + auto res = o1.transform([](int i) { return std::to_string(i); }); + + static_assert(std::is_same>::value, "bad map"); + DLIB_TEST(*res == "42"); + } + + { + auto res = o1.transform([](int i) {return i+1;}) + .transform([](int i) {return i*2;}) + .transform([](int i) {return std::to_string(i);}) + .transform([](const std::string& str) {return std::stoi(str);}) + .transform([](int i) {return i - 2;}) + .or_else([] {return dlib::make_optional(0);}); + + static_assert(std::is_same>::value, "bad map"); + DLIB_TEST(*res == 84); + } + + dlib::optional o2; + + { + auto res = o2.transform([](int i) {return i+1;}) + .or_else([] {return dlib::make_optional(0);}); + + static_assert(std::is_same>::value, "bad map"); + DLIB_TEST(*res == 0); + } + } + +// --------------------------------------------------------------------------------------------------- + + void test_optional_int_constexpr_monads() + { + constexpr dlib::optional o1{42}; + + { + struct callback + { + constexpr auto operator()(int i) {return dlib::optional{i}; }; + }; + + constexpr auto res = o1.and_then(callback{}); + + static_assert(std::is_same, dlib::optional>::value, "bad map"); + static_assert(*res == 42, "bad"); + DLIB_TEST(*res == 42); + } + } + +// --------------------------------------------------------------------------------------------------- + + static int constructor_count{0}; + static int copy_constructor_count{0}; + static int move_constructor_count{0}; + static int copy_assign_count{0}; + static int move_assign_count{0}; + + static void reset_counters() + { + constructor_count = 0; + copy_constructor_count = 0; + move_constructor_count = 0; + copy_assign_count = 0; + move_assign_count = 0; + } + + struct optional_dummy + { + optional_dummy() {++constructor_count;} + optional_dummy(const optional_dummy&) {++copy_constructor_count;} + optional_dummy(optional_dummy&&) {++move_constructor_count;} + optional_dummy& operator=(const optional_dummy&) {++copy_assign_count; return *this;} + optional_dummy& operator=(optional_dummy&&) {++move_assign_count; return *this;} + }; + + void test_constructors() + { + dlib::optional val; + DLIB_TEST(constructor_count == 0); + DLIB_TEST(copy_constructor_count == 0); + DLIB_TEST(move_constructor_count == 0); + DLIB_TEST(copy_assign_count == 0); + DLIB_TEST(move_assign_count == 0); + + val = optional_dummy{}; + DLIB_TEST(constructor_count == 1); + DLIB_TEST(copy_constructor_count == 0); + DLIB_TEST(move_constructor_count == 1); + DLIB_TEST(copy_assign_count == 0); + DLIB_TEST(move_assign_count == 0); + reset_counters(); + + dlib::optional val2{val}; + DLIB_TEST(constructor_count == 0); + DLIB_TEST(copy_constructor_count == 1); + DLIB_TEST(move_constructor_count == 0); + DLIB_TEST(copy_assign_count == 0); + DLIB_TEST(move_assign_count == 0); + reset_counters(); + + dlib::optional val3{std::move(val)}; + DLIB_TEST(constructor_count == 0); + DLIB_TEST(copy_constructor_count == 0); + DLIB_TEST(move_constructor_count == 1); + DLIB_TEST(copy_assign_count == 0); + DLIB_TEST(move_assign_count == 0); + reset_counters(); + + val2 = val; + DLIB_TEST(constructor_count == 0); + DLIB_TEST(copy_constructor_count == 0); + DLIB_TEST(move_constructor_count == 0); + DLIB_TEST(copy_assign_count == 1); + DLIB_TEST(move_assign_count == 0); + reset_counters(); + + val3 = std::move(val); + DLIB_TEST(constructor_count == 0); + DLIB_TEST(copy_constructor_count == 0); + DLIB_TEST(move_constructor_count == 0); + DLIB_TEST(copy_assign_count == 0); + DLIB_TEST(move_assign_count == 1); + reset_counters(); + } + +// --------------------------------------------------------------------------------------------------- + + void test_emplace() + { + struct A + { + A() = default; + explicit A(int i, float f, const std::string& str) : i_{i}, f_{f}, str_{str} {} + + int i_{0}; + float f_{0.0f}; + std::string str_; + }; + + dlib::optional o1(dlib::in_place, 1, 2.5f, "hello there"); + DLIB_TEST(o1); + DLIB_TEST(o1->i_ == 1); + DLIB_TEST(o1->f_ == 2.5f); + DLIB_TEST(o1->str_ == "hello there"); + + dlib::optional o2; + o2.emplace(2, 5.1f, "general kenobi"); + DLIB_TEST(o2); + DLIB_TEST(o2->i_ == 2); + DLIB_TEST(o2->f_ == 5.1f); + DLIB_TEST(o2->str_ == "general kenobi"); + + auto o3 = dlib::make_optional(3, 3.141592f, "from a certain point of view"); + DLIB_TEST(o3); + DLIB_TEST(o3->i_ == 3); + DLIB_TEST(o3->f_ == 3.141592f); + DLIB_TEST(o3->str_ == "from a certain point of view"); + + dlib::optional o4(dlib::in_place, {'a', 'b', 'c'}); + DLIB_TEST(o4); + DLIB_TEST(*o4 == "abc"); + DLIB_TEST(o4 == "abc"); + + dlib::optional o5; + o5.emplace({'a', 'b', 'c'}); + DLIB_TEST(o5); + DLIB_TEST(*o5 == "abc"); + } + +// --------------------------------------------------------------------------------------------------- + + void test_make_optional() + { + constexpr auto o1 = dlib::make_optional(1); + static_assert(std::is_same, dlib::optional>::value, "bad"); + static_assert(o1 == 1, "bad"); + static_assert(1 == o1, "bad"); + } + + class optional_tester : public tester + { + public: + optional_tester ( + ) : + tester ("test_optional", + "Runs tests on the optional object") + {} + + void perform_test ( + ) + { + test_optional_int(); + test_optional_int_constexpr(); + test_optional_int_monads(); + test_optional_int_constexpr_monads(); + test_constructors(); + test_emplace(); + test_make_optional(); + } + } a; +} \ No newline at end of file diff --git a/dlib/utility.h b/dlib/utility.h index 23bc82ec9..e8236bdbf 100644 --- a/dlib/utility.h +++ b/dlib/utility.h @@ -4,7 +4,7 @@ #define DLIB_UTILITY_Hh_ #include -#include +#include /* This header contains back-ports of C++14/17 functions and type traits @@ -67,6 +67,15 @@ namespace dlib using pop_front_t = typename pop_front::type; // --------------------------------------------------------------------- + + struct in_place_t + { + explicit in_place_t() = default; + }; + + static constexpr in_place_t in_place{}; + + // --------------------------------------------------------------------- } #endif //DLIB_UTILITY_Hh_