Skip to content

Commit

Permalink
New lean'n'mean optional<>
Browse files Browse the repository at this point in the history
  • Loading branch information
vector-of-bool committed Oct 17, 2023
1 parent 50a9a2c commit 3cb7cb2
Show file tree
Hide file tree
Showing 7 changed files with 1,029 additions and 47 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
_build/
.vscode/
.vscode/
.cache/
141 changes: 141 additions & 0 deletions src/neo/detail/optional.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#pragma once

#include "neo/type_traits.hpp"
#include <neo/concepts.hpp>

namespace neo {

template <typename T>
struct tombstone_traits;

template <typename T>
class optional;

/**
* @brief Detect whether T is a specialization of neo::optional
*/
template <typename T>
constexpr bool is_optional_v = false;

template <typename T>
constexpr bool is_optional_v<optional<T>> = true;

} // namespace neo

namespace neo::opt_detail {

template <typename Traits, typename T>
concept valid_tombstone_traits = requires {
typename Traits::state_type;
requires object_type<typename Traits::state_type>;
} and requires(typename Traits::state_type& mstate, typename Traits::state_type const& cstate) {
{ Traits::has_value(cstate) } noexcept -> simple_boolean;
{ Traits::get(cstate) } noexcept -> convertible_to<add_const_reference_t<T>>;
{ Traits::get(mstate) } noexcept -> convertible_to<add_lvalue_reference_t<T>>;
{ Traits::destroy(mstate) } noexcept;
{ Traits::trivial_copy } -> simple_boolean;
{ Traits::trivial_copy_assign } -> simple_boolean;
{ Traits::trivial_move } -> simple_boolean;
{ Traits::trivial_move_assign } -> simple_boolean;
};

template <class_type T>
struct inherit_from : T {};

// Hack: A concept that matches a type T that is a in_place_t tag
template <typename T>
concept in_place_t_tag = requires {
// Check for an injected-name matching in_place_t
typename inherit_from<T>::in_place_t;
}
// And that the nested name is the same as the enclosing class
&&weak_same_as<T, typename inherit_from<T>::in_place_t>;

/// Match a type named "nullopt_t"
template <typename T>
concept nullopt_t_tag = requires {
typename inherit_from<T>::nullopt_t;
requires weak_same_as<typename inherit_from<T>::nullopt_t, T>;
};

[[noreturn]] void throw_bad_optional();
[[noreturn]] void terminate_bad_optional();

/**
* This class defines the comparison operators for neo::optional. They are injected
* via ADL from inheritance, to prevent left/right ambiguity during overload selection.
*/
class adl_operators {
template <typename T, equality_comparable_with<T> U>
friend constexpr bool operator==(const optional<T>& self, const optional<U>& other) noexcept {
if (self.has_value() != other.has_value()) {
return false;
}
return not self.has_value() or *self == *other;
}

template <typename T>
friend constexpr bool operator==(const optional<T>& self,
opt_detail::nullopt_t_tag auto) noexcept {
return not self.has_value();
}

template <typename T, typename U>
requires(not is_optional_v<U>) and equality_comparable_with<T, U>
friend constexpr bool operator==(const optional<T>& self, const U& other) noexcept {
return self.has_value() and *self == other;
}

/**
* @brief Compare an optional against another optional of a value_type
* that is comparable to the value_type of this optional.
*
* A null optional is always less than an engaged one, two null optionals
* are equivalent, and two engaged optionals are compared by the value
* that they contain.
*/
template <typename T, three_way_comparable_with<T> U>
friend constexpr std::compare_three_way_result_t<T, U>
operator<=>(const optional<T>& self, const optional<U> other) noexcept {
if (self.has_value() and other.has_value()) {
return std::compare_three_way{}(*self, *other);
} else {
return self.has_value() <=> other.has_value();
}
}

/**
* @brief Compare the optional with a null-constant.
*
* The null constant is always less-than any engaged optional, and equivalent
* to a null optional.
*/
template <typename T>
friend constexpr std::strong_ordering //
operator<=>(const optional<T>& self, opt_detail::nullopt_t_tag auto) noexcept {
if (self.has_value()) {
return std::strong_ordering::greater;
} else {
return std::strong_ordering::equal;
}
}

/**
* @brief Compare the optional with a value of another type which is comparable with the
* optional's value_type
*
* A null optional is always less-than any other value
*/
template <typename T, typename U>
requires(not is_optional_v<U>) and three_way_comparable_with<T, U>
friend constexpr std::compare_three_way_result_t<T, U> //
operator<=>(const optional<T>& self, const U& value) noexcept {
if (self.has_value()) {
return std::compare_three_way{}(*self, value);
} else {
return std::strong_ordering::less;
}
}
};

} // namespace neo::opt_detail
32 changes: 32 additions & 0 deletions src/neo/emplacement.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include "./fwd.hpp"

#include <memory>
#include <new>

namespace neo {

template <typename T, typename... Args>
requires requires(void* p, Args&&... args) { ::new (p) T(NEO_FWD(args)...); }
constexpr auto construct_at(T* addr,
Args&&... args) //
noexcept(noexcept(::new((void*)0) T(NEO_FWD(args)...))) {
return std::construct_at(addr, NEO_FWD(args)...);
}

template <typename T>
constexpr void destroy_at(T* addr) {
addr->~T();
}

#if defined __GNUC__ || defined _MSC_VER
// GCC, Clang, and MSVC all support constexpr placement-new, so we'll just expand a macro for it
#define NEO_CONSTRUCT_AT(Address, ...) (::std::construct_at(Address __VA_OPT__(, ) __VA_ARGS__))
#else
#define NEO_CONSTRUCT_AT ::neo::construct_at
#endif
// No compiler lets this "just work"
#define NEO_DESTROY_AT (::neo::destroy_at)

} // namespace neo
12 changes: 12 additions & 0 deletions src/neo/optional.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include "./optional.hpp"

#include "./assert.hpp"

#include <optional>

using namespace neo;

void opt_detail::throw_bad_optional() { throw std::bad_optional_access(); }
void opt_detail::terminate_bad_optional() {
neo_assert(expects, false, "Attempted to dereference a disengaged optional object");
}
Loading

0 comments on commit 3cb7cb2

Please sign in to comment.