Fix a few mistakes in option, and make it trivial when possible

This commit is contained in:
2025-01-05 15:25:45 +01:00
parent b53fc9038f
commit 8607772d4f
9 changed files with 179 additions and 216 deletions

View File

@ -16,7 +16,7 @@ class box
ASL_NO_UNIQUE_ADDRESS Allocator m_alloc;
public:
explicit constexpr box(niche)
explicit constexpr box(niche_t)
requires default_constructible<Allocator>
: m_ptr{nullptr}
, m_alloc{}
@ -78,7 +78,7 @@ public:
return m_ptr;
}
constexpr bool operator==(niche) const
constexpr bool operator==(niche_t) const
{
return m_ptr == nullptr;
}

View File

@ -1,60 +1,72 @@
#pragma once
#include "asl/layout.hpp"
#include "asl/memory.hpp"
#include "asl/meta.hpp"
#include "asl/utility.hpp"
#include "asl/memory.hpp"
namespace asl
{
template<is_object T>
class maybe_uninit
union maybe_uninit
{
union
{
alignas(align_of<T>) char m_storage[size_of<T>];
T m_value;
};
private:
T m_value;
public:
constexpr maybe_uninit() {} // NOLINT(*-member-init)
constexpr maybe_uninit() requires trivially_default_constructible<T> = default;
constexpr maybe_uninit() requires (!trivially_default_constructible<T>) {} // NOLINT
maybe_uninit(const maybe_uninit&) = delete;
maybe_uninit(maybe_uninit&&) = delete;
maybe_uninit& operator=(const maybe_uninit&) = delete;
maybe_uninit& operator=(maybe_uninit&&) = delete;
constexpr ~maybe_uninit() = default;
constexpr ~maybe_uninit() requires (!trivially_destructible<T>) {}
constexpr void* uninit_ptr() && = delete;
constexpr const void* uninit_ptr() const& { return m_storage; }
constexpr void* uninit_ptr() & { return m_storage; }
// @Safety Pointer must only be accessed when in initialized state.
constexpr T* init_ptr_unsafe() && = delete;
constexpr const T* init_ptr_unsafe() const& { return &m_value; }
constexpr T* init_ptr_unsafe() & { return &m_value; }
// @Safety Reference must only be accessed when in initialized state.
constexpr T&& as_init_unsafe() && { return ASL_MOVE(m_value); }
constexpr const T& as_init_unsafe() const& { return m_value; }
constexpr T& as_init_unsafe() & { return m_value; }
// @Safety Must be called only when in uninitialized state.
template<typename... Args>
constexpr void init_unsafe(Args&&... args) &
explicit constexpr maybe_uninit(in_place_t, Args&&... args)
requires constructible_from<T, Args&&...>
: m_value{ASL_FWD(args)...}
{}
constexpr maybe_uninit(const maybe_uninit&) requires trivially_copy_constructible<T> = default;
constexpr maybe_uninit(const maybe_uninit&) requires (!trivially_copy_constructible<T>) {} // NOLINT
constexpr maybe_uninit(maybe_uninit&&) requires trivially_move_constructible<T> = default;
constexpr maybe_uninit(maybe_uninit&&) requires (!trivially_move_constructible<T>) {} // NOLINT
constexpr maybe_uninit& operator=(const maybe_uninit&) requires trivially_copy_assignable<T> = default;
constexpr maybe_uninit& operator=(const maybe_uninit&) requires (!trivially_copy_assignable<T>) {}
constexpr maybe_uninit& operator=(maybe_uninit&&) requires trivially_move_assignable<T> = default;
constexpr maybe_uninit& operator=(maybe_uninit&&) requires (!trivially_move_assignable<T>) {}
constexpr ~maybe_uninit() requires trivially_destructible<T> = default;
constexpr ~maybe_uninit() requires (!trivially_destructible<T>) {} // NOLINT
// @Safety Value must not have been initialized yet
template<typename... Args>
constexpr void construct_unsafe(Args&&... args)
requires constructible_from<T, Args&&...>
{
construct_at<T>(uninit_ptr(), ASL_FWD(args)...);
construct_at<T>(&m_value, ASL_FWD(args)...);
}
// @Safety Must be called only when in initialized state.
constexpr void uninit_unsafe() &
// @Safety Value must have been initialized
template<typename U>
constexpr void assign_unsafe(U&& value)
requires assignable_from<T&, U&&>
{
destroy(init_ptr_unsafe());
m_value = ASL_FWD(value);
}
// @Safety Value must have been initialized
constexpr void destroy_unsafe()
{
if constexpr (!trivially_destructible<T>)
{
destroy(&m_value);
}
}
// @Safety Value must have been initialized
constexpr const T& as_init_unsafe() const& { return m_value; }
constexpr T& as_init_unsafe() & { return m_value; }
constexpr T&& as_init_unsafe() && { return ASL_MOVE(m_value); }
};
} // namespace asl

View File

@ -190,7 +190,7 @@ template<> struct _is_integer_helper<uint64_t> : true_type {};
template<typename T> concept is_integer = _is_integer_helper<un_cv_t<T>>::value;
template<typename T, typename U>
concept equality_comparable_with = requires (const un_cvref_t<T>& a, const un_cvref_t<T>& b)
concept equality_comparable_with = requires (const un_cvref_t<T>& a, const un_cvref_t<U>& b)
{
{ a == b } -> same_as<bool>;
{ b == a } -> same_as<bool>;
@ -200,16 +200,12 @@ concept equality_comparable_with = requires (const un_cvref_t<T>& a, const un_cv
template<typename T> concept equality_comparable = equality_comparable_with<T, T>;
struct niche {};
struct niche_t {};
template<typename T>
concept has_niche = constructible_from<T, niche> &&
requires (const T& value, niche n)
{
{ value == n } -> same_as<bool>;
};
concept has_niche = constructible_from<T, niche_t> && equality_comparable_with<T, niche_t>;
template<typename T>
concept is_niche = same_as<un_cvref_t<T>, niche>;
concept is_niche = same_as<un_cvref_t<T>, niche_t>;
} // namespace asl

View File

@ -22,34 +22,24 @@ namespace option_internal
{
template<typename T, typename U>
concept convertible_from_option =
convertible_from<T, option<U>&> &&
convertible_from<T, const option<U>&> &&
convertible_from<T, option<U>&&> &&
convertible_from<T, const option<U>&&>;
concept not_constructible_from_option =
!constructible_from<T, option<U>&> &&
!constructible_from<T, const option<U>&> &&
!constructible_from<T, option<U>&&> &&
!constructible_from<T, const option<U>&&>;
template<typename T, typename U>
concept constructible_from_option =
constructible_from<T, option<U>&> &&
constructible_from<T, const option<U>&> &&
constructible_from<T, option<U>&&> &&
constructible_from<T, const option<U>&&>;
template<typename T, typename U>
concept assignable_from_option =
assignable_from<T&, option<U>&> &&
assignable_from<T&, const option<U>&> &&
assignable_from<T&, option<U>&&> &&
assignable_from<T&, const option<U>&&>;
template<typename T, typename U>
concept convertible_constructible_from_option =
convertible_from_option<T, U> && constructible_from_option<T, U>;
concept not_assignable_from_option =
!assignable_from<T&, option<U>&> &&
!assignable_from<T&, const option<U>&> &&
!assignable_from<T&, option<U>&&> &&
!assignable_from<T&, const option<U>&&>;
template<typename T, typename U>
concept convertible_constructible_assignable_from_option =
convertible_constructible_from_option<T, U> && assignable_from_option<T, U>;
concept not_constructible_assignable_from_option =
not_constructible_from_option<T, U> &&
not_assignable_from_option<T, U>;
} // namespace option_internal
@ -65,31 +55,35 @@ class option
{
static constexpr bool kHasNiche = has_niche<T>;
static constexpr bool kHasInlinePayload = default_constructible<T> || kHasNiche;
using Storage = select_t<kHasInlinePayload, T, maybe_uninit<T>>;
using HasValueMarker = select_t<kHasNiche, empty, bool>;
Storage m_payload;
maybe_uninit<T> m_payload{};
ASL_NO_UNIQUE_ADDRESS HasValueMarker m_has_value{};
template<is_object U>
friend class option;
template<typename... Args>
constexpr void construct(Args&&... args)
{
ASL_ASSERT(!has_value());
if constexpr (kHasInlinePayload)
if constexpr (!kHasNiche)
{
m_payload = T(ASL_FWD(args)...);
m_payload.construct_unsafe(ASL_FWD(args)...);
m_has_value = true;
}
else
{
m_payload.init_unsafe(ASL_FWD(args)...);
}
if constexpr (!kHasNiche)
{
m_has_value = true;
if constexpr (move_assignable<T>)
{
m_payload.assign_unsafe(ASL_MOVE(T{ASL_FWD(args)...}));
}
else
{
m_payload.destroy_unsafe();
m_payload.construct_unsafe(ASL_FWD(args)...);
}
}
}
@ -97,119 +91,81 @@ class option
constexpr void assign(U&& arg)
{
ASL_ASSERT(has_value());
if constexpr (kHasInlinePayload)
{
m_payload = ASL_FWD(arg);
}
else
{
m_payload.as_init_unsafe() = ASL_FWD(arg);
}
m_payload.assign_unsafe(ASL_FWD(arg));
}
public:
using type = T;
constexpr option() : option(nullopt) {}
constexpr option() : option{nullopt} {}
// NOLINTNEXTLINE(*-explicit-conversions)
constexpr option(nullopt_t) requires (!kHasNiche) && trivially_default_constructible<T> {}
constexpr option(nullopt_t) requires (!kHasNiche) {}
// NOLINTNEXTLINE(*-explicit-conversions)
constexpr option(nullopt_t) requires (!kHasNiche) && (!trivially_default_constructible<T>)
: m_payload{}
{}
// NOLINTNEXTLINE(*-explicit-conversions)
constexpr option(nullopt_t) requires kHasNiche
: m_payload{niche{}}
{}
constexpr option(nullopt_t) requires kHasNiche : m_payload{in_place, niche_t{}} {}
template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>)
option(U&& value)
requires (
kHasNiche &&
constructible_from<T, U> &&
!same_as<un_cvref_t<U>, option> &&
!is_niche<U>
constructible_from<T, U&&> &&
!same_as<un_cvref_t<U>, option>
)
: m_payload(ASL_FWD(value))
: m_payload{in_place, ASL_FWD(value)}
{}
template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>)
constexpr explicit (!convertible_from<T, U&&>)
option(U&& value)
requires (
kHasInlinePayload &&
!kHasNiche &&
constructible_from<T, U> &&
!same_as<un_cvref_t<U>, option> &&
!is_niche<U>
constructible_from<T, U&&> &&
!is_option<U>
)
: m_payload(ASL_FWD(value))
: m_payload{in_place, ASL_FWD(value)}
, m_has_value{true}
{}
constexpr option(const option& other) requires trivially_copy_constructible<T> = default;
constexpr option(const option& other) requires (!copy_constructible<T>) = delete;
template<typename U = T>
constexpr explicit (!convertible_from<T, U&&>)
option(U&& value)
requires (
!kHasInlinePayload &&
constructible_from<T, U> &&
!same_as<un_cvref_t<U>, option> &&
!is_niche<U>
)
: option(nullopt)
constexpr option(const option& other)
requires copy_constructible<T> && (!trivially_copy_constructible<T>)
: option{nullopt}
{
construct(ASL_FWD(value));
if (other.has_value())
{
construct(other.m_payload.as_init_unsafe());
}
}
constexpr option(const option& other)
requires copy_constructible<T> && kHasInlinePayload = default;
constexpr option(option&& other) requires trivially_move_constructible<T> = default;
constexpr option(option&& other) requires (!move_constructible<T>) = delete;
constexpr option(const option& other)
requires copy_constructible<T> && (!kHasInlinePayload)
: option(nullopt)
constexpr option(option&& other)
requires move_constructible<T> && (!trivially_move_constructible<T>)
: option{nullopt}
{
if (other.has_value())
{
construct(other.value());
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
}
constexpr option(const option& other)
requires (!copy_constructible<T>) = delete;
constexpr option(option&& other)
requires move_constructible<T> && kHasInlinePayload = default;
constexpr option(option&& other)
requires move_constructible<T> && (!kHasInlinePayload)
: option(nullopt)
{
if (other.has_value())
{
construct(ASL_MOVE(other.value()));
}
}
constexpr option(option&& other)
requires (!move_constructible<T>) = delete;
template<typename U>
constexpr explicit (!convertible_from<T, const U&>)
option(const option<U>& other)
requires (
constructible_from<T, const U&> &&
!option_internal::convertible_constructible_from_option<T, U>
option_internal::not_constructible_from_option<T, U>
)
: option(nullopt)
: option{nullopt}
{
if (other.has_value())
{
construct(other.value());
construct(other.m_payload.as_init_unsafe());
}
}
@ -218,13 +174,13 @@ public:
option(option<U>&& other)
requires (
constructible_from<T, U&&> &&
!option_internal::convertible_constructible_from_option<T, U>
option_internal::not_constructible_from_option<T, U>
)
: option(nullopt)
: option{nullopt}
{
if (other.has_value())
{
construct(ASL_MOVE(other.value()));
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
}
@ -237,9 +193,9 @@ public:
template<typename U = T>
constexpr option& operator=(U&& value) &
requires (
assignable_from<T&, U> &&
constructible_from<T, U> &&
!same_as<un_cvref_t<U>, option>
assignable_from<T&, U&&> &&
constructible_from<T, U&&> &&
!is_option<U>
)
{
if (has_value())
@ -255,13 +211,13 @@ public:
}
constexpr option& operator=(const option& other) &
requires (!copy_assignable<T> || !copy_constructible<T>) = delete;
requires (!copy_assignable<T>) = delete;
constexpr option& operator=(const option& other) &
requires copy_assignable<T> && copy_constructible<T> && kHasInlinePayload = default;
requires trivially_copy_assignable<T> = default;
constexpr option& operator=(const option& other) &
requires copy_assignable<T> && copy_constructible<T> && (!kHasInlinePayload)
requires copy_assignable<T> && (!trivially_copy_constructible<T>)
{
if (&other == this) { return *this; }
@ -283,12 +239,15 @@ public:
return *this;
}
constexpr option& operator=(option&& other) &
requires move_assignable<T> && move_constructible<T> && kHasInlinePayload = default;
requires (!move_assignable<T>) = delete;
constexpr option& operator=(option&& other) &
requires move_assignable<T> && move_constructible<T> && (!kHasInlinePayload)
requires trivially_move_assignable<T> = default;
constexpr option& operator=(option&& other) &
requires move_assignable<T> && (!trivially_move_constructible<T>)
{
if (&other == this) { return *this; }
@ -316,18 +275,18 @@ public:
requires (
constructible_from<T, const U&> &&
assignable_from<T&, const U&> &&
!option_internal::convertible_constructible_assignable_from_option<T, U>
option_internal::not_constructible_assignable_from_option<T, U>
)
{
if (other.has_value())
{
if (has_value())
{
assign(other.value());
assign(other.m_payload.as_init_unsafe());
}
else
{
construct(other.value());
construct(other.m_payload.as_init_unsafe());
}
}
else if (has_value())
@ -341,20 +300,20 @@ public:
template<typename U = T>
constexpr option& operator=(option<U>&& other) &
requires (
constructible_from<T, U> &&
assignable_from<T&, U> &&
!option_internal::convertible_constructible_assignable_from_option<T, U>
constructible_from<T, U&&> &&
assignable_from<T&, U&&> &&
option_internal::not_constructible_assignable_from_option<T, U>
)
{
if (other.has_value())
{
if (has_value())
{
assign(ASL_MOVE(other).value());
assign(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
else
{
construct(ASL_MOVE(other).value());
construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
}
}
else if (has_value())
@ -364,8 +323,8 @@ public:
return *this;
}
constexpr ~option() = default;
constexpr ~option() requires trivially_destructible<T> = default;
constexpr ~option() requires (!trivially_destructible<T>)
{
reset();
@ -379,21 +338,18 @@ public:
{
if constexpr (move_assignable<T>)
{
m_payload = T(niche{});
m_payload.assign_unsafe(ASL_MOVE(T{niche_t{}}));
}
else
{
destroy(&m_payload);
construct_at<T>(&m_payload, niche{});
m_payload.destroy_unsafe();
m_payload.construct_unsafe(niche_t{});
}
}
else
{
m_has_value = false;
if constexpr (!kHasInlinePayload)
{
m_payload.uninit_unsafe();
}
m_payload.destroy_unsafe();
}
}
@ -401,7 +357,7 @@ public:
{
if constexpr (kHasNiche)
{
return m_payload != niche{};
return m_payload.as_init_unsafe() != niche_t{};
}
else
{
@ -413,40 +369,19 @@ public:
constexpr T&& value() &&
{
ASL_ASSERT_RELEASE(has_value());
if constexpr (kHasInlinePayload)
{
return ASL_MOVE(m_payload);
}
else
{
return ASL_MOVE(m_payload).as_init_unsafe();
}
return ASL_MOVE(m_payload).as_init_unsafe();
}
constexpr T& value() &
{
ASL_ASSERT_RELEASE(has_value());
if constexpr (kHasInlinePayload)
{
return m_payload;
}
else
{
return m_payload.as_init_unsafe();
}
return m_payload.as_init_unsafe();
}
constexpr const T& value() const&
{
ASL_ASSERT_RELEASE(has_value());
if constexpr (kHasInlinePayload)
{
return m_payload;
}
else
{
return m_payload.as_init_unsafe();
}
return m_payload.as_init_unsafe();
}
template<typename U>

View File

@ -55,6 +55,8 @@ ASL_TEST(arrow)
ASL_TEST(niche)
{
static_assert(sizeof(asl::box<int>) == sizeof(asl::option<asl::box<int>>));
asl::option<asl::box<int>> opt;
ASL_TEST_EXPECT(!opt.has_value());
@ -66,7 +68,7 @@ ASL_TEST(niche)
ASL_TEST_EXPECT(!opt.has_value());
bool destroyed = false;
asl::option<asl::box<DestructorObserver>> opt2 = asl::make_box<DestructorObserver>(&destroyed);
asl::option opt2 = asl::make_box<DestructorObserver>(&destroyed);
ASL_TEST_EXPECT(opt2.has_value());
ASL_TEST_EXPECT(!destroyed);

View File

@ -5,6 +5,18 @@ static_assert(asl::layout::of<int>() == asl::layout::of<asl::maybe_uninit<int>>(
static_assert(asl::size_of<int> == asl::size_of<asl::maybe_uninit<int>>);
static_assert(asl::align_of<int> == asl::align_of<asl::maybe_uninit<int>>);
static_assert(asl::trivially_destructible<asl::maybe_uninit<TrivialType>>);
static_assert(!asl::trivially_destructible<asl::maybe_uninit<WithDestructor>>);
#define TEST_TYPE_PROPERTIES(PRP) \
static_assert(asl::PRP<asl::maybe_uninit<TrivialType>> == asl::PRP<TrivialType>); \
static_assert(asl::PRP<asl::maybe_uninit<TrivialTypeDefaultValue>> == asl::PRP<TrivialTypeDefaultValue>); \
static_assert(asl::PRP<asl::maybe_uninit<WithDestructor>> == asl::PRP<WithDestructor>); \
static_assert(asl::PRP<asl::maybe_uninit<Copyable>> == asl::PRP<Copyable>); \
static_assert(asl::PRP<asl::maybe_uninit<MoveableOnly>> == asl::PRP<MoveableOnly>); \
static_assert(asl::PRP<asl::maybe_uninit<Pinned>> == asl::PRP<Pinned>);
TEST_TYPE_PROPERTIES(trivially_default_constructible);
TEST_TYPE_PROPERTIES(trivially_copy_constructible);
TEST_TYPE_PROPERTIES(trivially_move_constructible);
TEST_TYPE_PROPERTIES(trivially_copy_assignable);
TEST_TYPE_PROPERTIES(trivially_move_assignable);
TEST_TYPE_PROPERTIES(trivially_destructible);

View File

@ -14,14 +14,14 @@ struct NonZero
ASL_ASSERT(x != 0);
}
constexpr explicit NonZero(asl::niche) : value(0) {}
constexpr explicit NonZero(asl::niche_t) : value(0) {}
constexpr bool operator==(asl::niche) const { return value == 0; }
constexpr bool operator==(asl::niche_t) const { return value == 0; }
};
static_assert(asl::has_niche<NonZero>);
static_assert(!asl::has_niche<int>);
static_assert(sizeof(asl::option<int>) > sizeof(int));
static_assert(sizeof(asl::option<int>) >= sizeof(int));
static_assert(sizeof(asl::option<NonZero>) == sizeof(NonZero));
static_assert(!asl::is_option<int>);
@ -31,22 +31,26 @@ static_assert(asl::is_option<const asl::option<int>>);
static_assert(asl::trivially_destructible<asl::option<TrivialType>>);
static_assert(!asl::trivially_destructible<asl::option<WithDestructor>>);
static_assert(asl::copy_constructible<asl::option<int>>);
static_assert(asl::trivially_copy_constructible<asl::option<int>>);
static_assert(asl::trivially_copy_constructible<asl::option<NonZero>>);
static_assert(asl::copy_constructible<asl::option<Copyable>>);
static_assert(!asl::copy_constructible<asl::option<MoveableOnly>>);
static_assert(!asl::copy_constructible<asl::option<Pinned>>);
static_assert(asl::move_constructible<asl::option<int>>);
static_assert(asl::trivially_move_constructible<asl::option<int>>);
static_assert(asl::trivially_move_constructible<asl::option<NonZero>>);
static_assert(asl::move_constructible<asl::option<Copyable>>);
static_assert(asl::move_constructible<asl::option<MoveableOnly>>);
static_assert(!asl::move_constructible<asl::option<Pinned>>);
static_assert(asl::copy_assignable<asl::option<int>>);
static_assert(asl::trivially_copy_assignable<asl::option<int>>);
static_assert(asl::trivially_copy_assignable<asl::option<NonZero>>);
static_assert(asl::copy_assignable<asl::option<Copyable>>);
static_assert(!asl::copy_assignable<asl::option<MoveableOnly>>);
static_assert(!asl::copy_assignable<asl::option<Pinned>>);
static_assert(asl::move_assignable<asl::option<int>>);
static_assert(asl::trivially_move_assignable<asl::option<int>>);
static_assert(asl::trivially_move_assignable<asl::option<NonZero>>);
static_assert(asl::move_assignable<asl::option<Copyable>>);
static_assert(asl::move_assignable<asl::option<MoveableOnly>>);
static_assert(!asl::move_assignable<asl::option<Pinned>>);

View File

@ -11,6 +11,9 @@
namespace asl
{
struct in_place_t {};
static constexpr in_place_t in_place{};
template<moveable T>
constexpr void swap(T& a, T& b)
{

View File

@ -2,6 +2,5 @@ hashing
hash_set
hash_map
logging
status
status_or
dynlib