More work on option
This commit is contained in:
@ -29,3 +29,4 @@ Checks:
|
||||
- "-*-signed-bitwise"
|
||||
- "-readability-use-anyofallof"
|
||||
- "-readability-function-cognitive-complexity"
|
||||
# - "-*-rvalue-reference-param-not-moved"
|
||||
|
@ -67,6 +67,9 @@ template<typename T> concept trivially_destructible = __is_trivially_destructibl
|
||||
|
||||
template<typename T> concept trivially_copyable = __is_trivially_copyable(T);
|
||||
|
||||
template<typename From, typename To>
|
||||
concept convertible = __is_convertible(From, To);
|
||||
|
||||
using nullptr_t = decltype(nullptr);
|
||||
|
||||
template<typename T> struct _un_const_helper { using type = T; };
|
||||
@ -81,6 +84,8 @@ template<typename T> using un_volatile_t = _un_volatile_helper<T>::type;
|
||||
|
||||
template<typename T> using un_cv_t = un_volatile_t<un_const_t<T>>;
|
||||
|
||||
template<typename T> using un_cvref_t = un_ref_t<un_cv_t<T>>;
|
||||
|
||||
template<typename T> concept is_void = is_same<void, un_cv_t<T>>;
|
||||
|
||||
template<typename T> struct _is_ref_helper { static constexpr bool l = false; static constexpr bool r = false; };
|
||||
|
263
asl/option.hpp
263
asl/option.hpp
@ -15,39 +15,117 @@ static constexpr nullopt_t nullopt{};
|
||||
// @Todo(option) Function
|
||||
// @Todo(option) Arrays
|
||||
|
||||
template<is_object T> class option;
|
||||
|
||||
namespace option_internal
|
||||
{
|
||||
|
||||
template<typename T, typename U>
|
||||
concept convertible_from_option =
|
||||
convertible<option<U>&, T> &&
|
||||
convertible<const option<U>&, T> &&
|
||||
convertible<option<U>&&, T> &&
|
||||
convertible<const option<U>&&, T>;
|
||||
|
||||
template<typename T, typename U>
|
||||
concept constructible_from_option =
|
||||
constructible<T, option<U>&> &&
|
||||
constructible<T, const option<U>&> &&
|
||||
constructible<T, option<U>&&> &&
|
||||
constructible<T, const option<U>&&>;
|
||||
|
||||
template<typename T, typename U>
|
||||
concept assignable_from_option =
|
||||
assignable<T&, option<U>&> &&
|
||||
assignable<T&, const option<U>&> &&
|
||||
assignable<T&, option<U>&&> &&
|
||||
assignable<T&, const option<U>&&>;
|
||||
|
||||
template<typename T, typename U>
|
||||
concept convertible_constructible_from_option =
|
||||
convertible_from_option<T, U> && constructible_from_option<T, U>;
|
||||
|
||||
|
||||
template<typename T, typename U>
|
||||
concept convertible_constructible_assignable_from_option =
|
||||
convertible_constructible_from_option<T, U> && assignable_from_option<T, U>;
|
||||
|
||||
} // namespace option_internal
|
||||
|
||||
template<typename T>
|
||||
concept is_option = requires
|
||||
{
|
||||
typename T::type;
|
||||
requires is_same<un_cvref_t<T>, option<typename T::type>>;
|
||||
};
|
||||
|
||||
template<is_object T>
|
||||
class option
|
||||
{
|
||||
maybe_uninit<T> m_payload;
|
||||
bool m_has_value = false;
|
||||
static constexpr bool kIsTrivial =
|
||||
trivially_default_constructible<T> &&
|
||||
trivially_copy_constructible<T> &&
|
||||
trivially_move_constructible<T> &&
|
||||
trivially_copy_assignable<T> &&
|
||||
trivially_move_assignable<T> &&
|
||||
trivially_destructible<T>;
|
||||
|
||||
using Storage = select_t<kIsTrivial, T, maybe_uninit<T>>;
|
||||
|
||||
Storage m_payload{};
|
||||
bool m_has_value = false;
|
||||
|
||||
template<typename... Args>
|
||||
constexpr void construct(Args&&... args) &
|
||||
{
|
||||
ASL_ASSERT(!m_has_value);
|
||||
m_payload.init_unsafe(ASL_FWD(args)...);
|
||||
m_has_value = true;
|
||||
if constexpr (kIsTrivial)
|
||||
{
|
||||
new(&m_payload) T(ASL_FWD(args)...);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_payload.init_unsafe(ASL_FWD(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Arg>
|
||||
constexpr void assign(Arg&& arg) &
|
||||
{
|
||||
ASL_ASSERT(m_has_value);
|
||||
m_payload.as_init_unsafe() = ASL_FWD(arg);
|
||||
if constexpr (kIsTrivial)
|
||||
{
|
||||
m_payload = ASL_FWD(arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_payload.as_init_unsafe() = ASL_FWD(arg);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
using type = T;
|
||||
|
||||
constexpr option() = default;
|
||||
constexpr option(nullopt_t) {} // NOLINT(*-explicit-conversions)
|
||||
|
||||
template<typename U>
|
||||
// NOLINTNEXTLINE(*-explicit-conversions)
|
||||
constexpr option(U&& value) requires constructible<T, U>
|
||||
template<typename U = T>
|
||||
constexpr explicit (!convertible<U&&, T>)
|
||||
option(U&& value)
|
||||
requires (
|
||||
constructible<T, U> &&
|
||||
!is_same<un_cvref_t<U>, option>
|
||||
)
|
||||
{
|
||||
construct(ASL_FWD(value));
|
||||
}
|
||||
|
||||
constexpr option(const option& other)
|
||||
requires copy_constructible<T> && kIsTrivial = default;
|
||||
|
||||
constexpr option(const option& other) requires copy_constructible<T>
|
||||
constexpr option(const option& other)
|
||||
requires copy_constructible<T> && (!kIsTrivial)
|
||||
{
|
||||
if (other.m_has_value)
|
||||
{
|
||||
@ -55,7 +133,14 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
constexpr option(option&& other) requires move_constructible<T>
|
||||
constexpr option(const option& other)
|
||||
requires (!copy_constructible<T>) = delete;
|
||||
|
||||
constexpr option(option&& other)
|
||||
requires move_constructible<T> && kIsTrivial = default;
|
||||
|
||||
constexpr option(option&& other)
|
||||
requires move_constructible<T> && (!kIsTrivial)
|
||||
{
|
||||
if (other.m_has_value)
|
||||
{
|
||||
@ -63,7 +148,68 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
constexpr option& operator=(const option& other) & requires copy_assignable<T> && copy_constructible<T>
|
||||
template<typename U>
|
||||
constexpr explicit (!convertible<const U&, T>)
|
||||
option(const option<U>& other)
|
||||
requires (
|
||||
constructible<T, const U&> &&
|
||||
!option_internal::convertible_constructible_from_option<T, U>
|
||||
)
|
||||
{
|
||||
if (other.has_value())
|
||||
{
|
||||
construct(other.value());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
constexpr explicit (!convertible<U&&, T>)
|
||||
option(option<U>&& other)
|
||||
requires (
|
||||
constructible<T, U&&> &&
|
||||
!option_internal::convertible_constructible_from_option<T, U>
|
||||
)
|
||||
{
|
||||
if (other.has_value())
|
||||
{
|
||||
construct(ASL_MOVE(other).value());
|
||||
}
|
||||
}
|
||||
|
||||
constexpr option& operator=(nullopt_t) &
|
||||
{
|
||||
reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U = T>
|
||||
constexpr option& operator=(U&& value) &
|
||||
requires (
|
||||
assignable<T&, U> &&
|
||||
constructible<T, U> &&
|
||||
!is_same<un_cvref_t<U>, option>
|
||||
)
|
||||
{
|
||||
if (m_has_value)
|
||||
{
|
||||
assign(ASL_FWD(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
construct(ASL_FWD(value));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr option& operator=(const option& other) &
|
||||
requires (!copy_assignable<T> || copy_constructible<T>) = delete;
|
||||
|
||||
constexpr option& operator=(const option& other) &
|
||||
requires copy_assignable<T> && copy_constructible<T> && kIsTrivial = default;
|
||||
|
||||
constexpr option& operator=(const option& other) &
|
||||
requires copy_assignable<T> && copy_constructible<T> && (!kIsTrivial)
|
||||
{
|
||||
if (&other == this) { return *this; }
|
||||
|
||||
@ -78,7 +224,7 @@ public:
|
||||
construct(other.m_payload.as_init_unsafe());
|
||||
}
|
||||
}
|
||||
else if (m_has_value)
|
||||
else
|
||||
{
|
||||
reset();
|
||||
}
|
||||
@ -86,7 +232,11 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr option& operator=(option&& other) & requires move_assignable<T> && move_constructible<T>
|
||||
constexpr option& operator=(option&& other) &
|
||||
requires move_assignable<T> && move_constructible<T> && kIsTrivial = default;
|
||||
|
||||
constexpr option& operator=(option&& other) &
|
||||
requires move_assignable<T> && move_constructible<T> && (!kIsTrivial)
|
||||
{
|
||||
if (&other == this) { return *this; }
|
||||
|
||||
@ -101,13 +251,67 @@ public:
|
||||
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
|
||||
}
|
||||
}
|
||||
else if (m_has_value)
|
||||
else
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U = T>
|
||||
constexpr option& operator=(const option<U>& other) &
|
||||
requires (
|
||||
constructible<T, const U&> &&
|
||||
assignable<T&, const U&> &&
|
||||
!option_internal::convertible_constructible_assignable_from_option<T, U>
|
||||
)
|
||||
{
|
||||
if (other.has_value())
|
||||
{
|
||||
if (m_has_value)
|
||||
{
|
||||
assign(other.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
construct(other.value());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U = T>
|
||||
constexpr option& operator=(option<U>&& other) &
|
||||
requires (
|
||||
constructible<T, U> &&
|
||||
assignable<T&, U> &&
|
||||
!option_internal::convertible_constructible_assignable_from_option<T, U>
|
||||
)
|
||||
{
|
||||
if (other.has_value())
|
||||
{
|
||||
if (m_has_value)
|
||||
{
|
||||
assign(ASL_MOVE(other).value());
|
||||
}
|
||||
else
|
||||
{
|
||||
construct(ASL_MOVE(other).value());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr ~option() = default;
|
||||
constexpr ~option() requires (!trivially_destructible<T>)
|
||||
@ -117,7 +321,11 @@ public:
|
||||
|
||||
constexpr void reset()
|
||||
{
|
||||
if (m_has_value)
|
||||
if constexpr (kIsTrivial)
|
||||
{
|
||||
m_has_value = false;
|
||||
}
|
||||
else if (m_has_value)
|
||||
{
|
||||
m_payload.uninit_unsafe();
|
||||
m_has_value = false;
|
||||
@ -130,19 +338,40 @@ public:
|
||||
constexpr T&& value() &&
|
||||
{
|
||||
ASL_ASSERT(m_has_value); // @Todo Release assert
|
||||
return ASL_MOVE(m_payload).as_init_unsafe();
|
||||
if constexpr (kIsTrivial)
|
||||
{
|
||||
return ASL_MOVE(m_payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ASL_MOVE(m_payload).as_init_unsafe();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr T& value() &
|
||||
{
|
||||
ASL_ASSERT(m_has_value); // @Todo Release assert
|
||||
return m_payload.as_init_unsafe();
|
||||
if constexpr (kIsTrivial)
|
||||
{
|
||||
return m_payload;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_payload.as_init_unsafe();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const T& value() const&
|
||||
{
|
||||
ASL_ASSERT(m_has_value); // @Todo Release assert
|
||||
return m_payload.as_init_unsafe();
|
||||
if constexpr (kIsTrivial)
|
||||
{
|
||||
return m_payload;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_payload.as_init_unsafe();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -168,3 +168,15 @@ static_assert(!asl::trivially_copyable<HasDestructor>);
|
||||
static_assert(!asl::trivially_copyable<CopyAssignable>);
|
||||
static_assert(asl::trivially_copyable<DefaultConstructible>);
|
||||
static_assert(asl::trivially_copyable<TriviallyDefaultConstructible>);
|
||||
|
||||
class Base {};
|
||||
class Derived : public Base {};
|
||||
class C {};
|
||||
class D { public: operator C() { return c; } C c; };
|
||||
class E { public: template<class T> E(T&&) {} };
|
||||
|
||||
static_assert(asl::convertible<Derived*, Base*>);
|
||||
static_assert(!asl::convertible<Base*, Derived*>);
|
||||
static_assert(asl::convertible<D, C>);
|
||||
static_assert(!asl::convertible<Derived*, C*>);
|
||||
static_assert(asl::convertible<Base, E>);
|
||||
|
@ -1,6 +1,13 @@
|
||||
#include "asl/option.hpp"
|
||||
#include "asl/tests/test_types.hpp"
|
||||
#include "asl/testing/testing.hpp"
|
||||
#include "asl/testing/testing.hpp"
|
||||
|
||||
class Base {};
|
||||
class Derived : public Base {};
|
||||
|
||||
static_assert(!asl::is_option<int>);
|
||||
static_assert(asl::is_option<asl::option<int>>);
|
||||
static_assert(asl::is_option<const asl::option<int>>);
|
||||
|
||||
static_assert(asl::trivially_destructible<asl::option<TriviallyDestructible>>);
|
||||
static_assert(!asl::trivially_destructible<asl::option<HasDestructor>>);
|
||||
@ -22,8 +29,39 @@ static_assert(!asl::copy_assignable<asl::option<NonMoveAssignable>>);
|
||||
|
||||
static_assert(asl::move_assignable<asl::option<int>>);
|
||||
static_assert(asl::move_assignable<asl::option<CopyAssignable>>);
|
||||
static_assert(asl::move_assignable<asl::option<MoveAssignable>>);
|
||||
static_assert(!asl::move_assignable<asl::option<NonMoveAssignable>>);
|
||||
|
||||
static_assert(asl::assignable<asl::option<Base*>&, asl::option<Derived*>>);
|
||||
static_assert(!asl::assignable<asl::option<Derived*>&, asl::option<Base*>>);
|
||||
|
||||
static_assert(!asl::convertible<asl::option<Base*>, asl::option<Derived*>>);
|
||||
static_assert(asl::convertible<asl::option<Derived*>, asl::option<Base*>>);
|
||||
|
||||
class ExplicitConversion { public: explicit ExplicitConversion(int) {} };
|
||||
class ImplicitConversion { public: ImplicitConversion(int) {} }; // NOLINT
|
||||
|
||||
static_assert(!asl::convertible<int, ExplicitConversion>);
|
||||
static_assert(asl::convertible<int, ImplicitConversion>);
|
||||
|
||||
static_assert(!asl::convertible<int, asl::option<ExplicitConversion>>);
|
||||
static_assert(asl::convertible<int, asl::option<ImplicitConversion>>);
|
||||
|
||||
static_assert(!asl::convertible<asl::option<int>, asl::option<ExplicitConversion>>);
|
||||
static_assert(asl::convertible<asl::option<int>, asl::option<ImplicitConversion>>);
|
||||
|
||||
static_assert(asl::trivially_copy_constructible<asl::option<int>>);
|
||||
static_assert(!asl::trivially_copy_constructible<asl::option<CopyConstructible>>);
|
||||
|
||||
static_assert(asl::trivially_move_constructible<asl::option<int>>);
|
||||
static_assert(!asl::trivially_move_constructible<asl::option<MoveConstructible>>);
|
||||
|
||||
static_assert(asl::trivially_copy_assignable<asl::option<int>>);
|
||||
static_assert(!asl::trivially_copy_assignable<asl::option<CopyAssignable>>);
|
||||
|
||||
static_assert(asl::trivially_move_assignable<asl::option<int>>);
|
||||
static_assert(!asl::trivially_move_assignable<asl::option<MoveAssignable>>);
|
||||
|
||||
ASL_TEST(make_null)
|
||||
{
|
||||
asl::option<int> a;
|
||||
@ -111,3 +149,43 @@ ASL_TEST(deduction_guide)
|
||||
asl::option opt(45);
|
||||
ASL_TEST_EXPECT(opt.value() == 45);
|
||||
}
|
||||
|
||||
ASL_TEST(convert_copy)
|
||||
{
|
||||
asl::option<uint8_t> opt8 = uint8_t{8};
|
||||
asl::option<uint16_t> opt16 = opt8;
|
||||
|
||||
ASL_TEST_EXPECT(opt16.has_value());
|
||||
ASL_TEST_EXPECT(opt16.value() == 8);
|
||||
|
||||
opt8 = uint8_t{10};
|
||||
ASL_TEST_EXPECT(opt8.has_value());
|
||||
ASL_TEST_EXPECT(opt8.value() == 10);
|
||||
|
||||
opt16 = asl::nullopt;
|
||||
ASL_TEST_EXPECT(!opt16.has_value());
|
||||
|
||||
opt16 = opt8;
|
||||
ASL_TEST_EXPECT(opt16.has_value());
|
||||
ASL_TEST_EXPECT(opt16.value() == 10);
|
||||
}
|
||||
|
||||
ASL_TEST(convert_move)
|
||||
{
|
||||
asl::option<uint8_t> opt8 = uint8_t{8};
|
||||
asl::option<uint16_t> opt16 = ASL_MOVE(opt8);
|
||||
|
||||
ASL_TEST_EXPECT(opt16.has_value());
|
||||
ASL_TEST_EXPECT(opt16.value() == 8);
|
||||
|
||||
opt8 = ASL_MOVE(uint8_t{10});
|
||||
ASL_TEST_EXPECT(opt8.has_value());
|
||||
ASL_TEST_EXPECT(opt8.value() == 10);
|
||||
|
||||
opt16 = asl::nullopt;
|
||||
ASL_TEST_EXPECT(!opt16.has_value());
|
||||
|
||||
opt16 = ASL_MOVE(opt8);
|
||||
ASL_TEST_EXPECT(opt16.has_value());
|
||||
ASL_TEST_EXPECT(opt16.value() == 10);
|
||||
}
|
||||
|
@ -14,11 +14,11 @@ struct MoveConstructible { MoveConstructible(MoveConstructible&&) {} };
|
||||
struct TriviallyMoveConstructible { TriviallyMoveConstructible(TriviallyMoveConstructible&&) = default; };
|
||||
struct NonMoveConstructible { NonMoveConstructible(NonMoveConstructible&&) = delete; };
|
||||
|
||||
struct CopyAssignable { CopyAssignable& operator=(const CopyAssignable&) { return *this; } };
|
||||
struct CopyAssignable { CopyAssignable(const CopyAssignable&) {} CopyAssignable& operator=(const CopyAssignable&) { return *this; } };
|
||||
struct TriviallyCopyAssignable { TriviallyCopyAssignable& operator=(const TriviallyCopyAssignable&) = default; };
|
||||
struct NonCopyAssignable { NonCopyAssignable& operator=(const NonCopyAssignable&) = delete; };
|
||||
|
||||
struct MoveAssignable { MoveAssignable& operator=(MoveAssignable&&) { return *this; } };
|
||||
struct MoveAssignable { MoveAssignable(MoveAssignable&&) {} MoveAssignable& operator=(MoveAssignable&&) { return *this; } };
|
||||
struct TriviallyMoveAssignable { TriviallyMoveAssignable& operator=(TriviallyMoveAssignable&&) = default; };
|
||||
struct NonMoveAssignable { NonMoveAssignable& operator=(NonMoveAssignable&&) = delete; };
|
||||
|
||||
|
Reference in New Issue
Block a user