Skip to content

Commit 479d1e7

Browse files
committed
refactor(lib): polymorphic is value-type
This commit adjusts the Polymorphic class so that it becomes a value-type that matches the standard std::polymorphic. It also implements an implementation detail shared with Optional, allowing the private nullable state to be used for storage optimization. The semantics become less convenient in the case of optional polymorphic objects, but the application becomes much safer because non-nullable objects are never in an invalid state. After adapting all objects, most polymorphic objects are, in fact, not nullable. As future work, we can define a custom type for nullable polymorphic objects that has simpler semantics.
1 parent c9f9ba1 commit 479d1e7

File tree

2 files changed

+499
-142
lines changed

2 files changed

+499
-142
lines changed

include/mrdocs/ADT/Optional.hpp

Lines changed: 326 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include <mrdocs/Platform.hpp>
1717
#include <mrdocs/ADT/Nullable.hpp>
18+
#include <compare>
1819
#include <optional>
1920
#include <type_traits>
2021
#include <utility>
@@ -83,6 +84,11 @@ class Optional {
8384
}())
8485
{}
8586

87+
/** Construct from std::nullopt
88+
*/
89+
constexpr Optional(std::nullopt_t) noexcept(default_ctor_noex_())
90+
: Optional() {}
91+
8692
/// Copy constructor
8793
constexpr Optional(Optional const&) = default;
8894

@@ -102,15 +108,18 @@ class Optional {
102108
@param u The value to store. It must be convertible to T.
103109
**/
104110
template <class U>
105-
requires std::is_constructible_v<T, U>
111+
requires(
112+
!std::same_as<std::remove_cvref_t<U>, Optional>
113+
&& !std::same_as<std::remove_cvref_t<U>, std::in_place_t>
114+
&& !std::same_as<std::remove_cvref_t<U>, std::nullopt_t>
115+
&& std::is_constructible_v<T, U>)
106116
constexpr explicit Optional(U&& u) noexcept(
107117
std::is_nothrow_constructible_v<T, U>)
108118
: s_([&] {
109119
if constexpr (uses_nullable_traits)
110120
{
111121
return storage_t(static_cast<T>(std::forward<U>(u)));
112-
}
113-
else
122+
} else
114123
{
115124
return storage_t(std::forward<U>(u));
116125
}
@@ -122,11 +131,20 @@ class Optional {
122131
@param u The value to store. It must be convertible to T.
123132
**/
124133
template <class U>
125-
requires std::is_constructible_v<T, U> && std::is_assignable_v<T&, U>
126-
constexpr Optional&
134+
requires(
135+
!std::same_as<std::remove_cvref_t<U>, Optional>
136+
&& std::is_constructible_v<T, U> && std::is_assignable_v<T&, U>)
137+
constexpr
138+
Optional&
127139
operator=(U&& u) noexcept(std::is_nothrow_assignable_v<T&, U>)
128140
{
129-
s_ = std::forward<U>(u);
141+
if constexpr (uses_nullable_traits)
142+
{
143+
s_ = std::forward<U>(u);
144+
} else
145+
{
146+
s_ = std::forward<U>(u);
147+
}
130148
return *this;
131149
}
132150

@@ -330,15 +348,311 @@ class Optional {
330348
return *s_;
331349
}
332350
}
351+
};
333352

334-
/** Three-way comparison defaults when supported by the underlying
335-
T/std::optional.
336-
**/
337-
auto
338-
operator<=>(Optional const&) const
339-
= default;
353+
namespace detail {
354+
template<typename T>
355+
inline constexpr bool isOptionalV = false;
356+
357+
template<typename T>
358+
inline constexpr bool isOptionalV<Optional<T>> = true;
359+
360+
template <typename T>
361+
using OptionalRelopT = std::enable_if_t<std::is_convertible_v<T, bool>, bool>;
362+
363+
template <typename T, typename U>
364+
using OptionalEqT = OptionalRelopT<
365+
decltype(std::declval<T const&>() == std::declval<U const&>())>;
366+
367+
template <typename T, typename U>
368+
using OptionalNeT = OptionalRelopT<
369+
decltype(std::declval<T const&>() != std::declval<U const&>())>;
370+
371+
template <typename T, typename U>
372+
using OptionalLtT = OptionalRelopT<
373+
decltype(std::declval<T const&>() < std::declval<U const&>())>;
374+
375+
template <typename T, typename U>
376+
using OptionalGtT = OptionalRelopT<
377+
decltype(std::declval<T const&>() > std::declval<U const&>())>;
378+
379+
template <typename T, typename U>
380+
using OptionalLeT = OptionalRelopT<
381+
decltype(std::declval<T const&>() <= std::declval<U const&>())>;
382+
383+
template <typename T, typename U>
384+
using OptionalGeT = detail::OptionalRelopT<
385+
decltype(std::declval<T const&>() >= std::declval<U const&>())>;
386+
387+
template <typename T>
388+
concept isDerivedFromOptional = requires(T const& __t) {
389+
[]<typename U>(Optional<U> const&) {
390+
}(__t);
340391
};
341392

393+
} // namespace detail
394+
395+
/** Compares two Optional values for equality.
396+
397+
Returns true if both are engaged and their contained values are equal, or both are disengaged.
398+
*/
399+
template <typename T, typename U>
400+
constexpr detail::OptionalEqT<T, U>
401+
operator==(Optional<T> const& lhs, Optional<U> const& rhs)
402+
{
403+
return static_cast<bool>(lhs) == static_cast<bool>(rhs)
404+
&& (!lhs || *lhs == *rhs);
405+
}
406+
407+
/** Compares two Optional values for inequality.
408+
409+
Returns true if their engagement states differ or their contained values are not equal.
410+
*/
411+
template <typename T, typename U>
412+
constexpr detail::OptionalNeT<T, U>
413+
operator!=(Optional<T> const& lhs, Optional<U> const& rhs)
414+
{
415+
return static_cast<bool>(lhs) != static_cast<bool>(rhs)
416+
|| (static_cast<bool>(lhs) && *lhs != *rhs);
417+
}
418+
419+
/** Checks if the left Optional is less than the right Optional.
420+
421+
Returns true if the right is engaged and either the left is disengaged or its value is less.
422+
*/
423+
template <typename T, typename U>
424+
constexpr detail::OptionalLtT<T, U>
425+
operator<(Optional<T> const& lhs, Optional<U> const& rhs)
426+
{
427+
return static_cast<bool>(rhs) && (!lhs || *lhs < *rhs);
428+
}
429+
430+
/** Checks if the left Optional is greater than the right Optional.
431+
432+
Returns true if the left is engaged and either the right is disengaged or its value is greater.
433+
*/
434+
template <typename T, typename U>
435+
constexpr detail::OptionalGtT<T, U>
436+
operator>(Optional<T> const& lhs, Optional<U> const& rhs)
437+
{
438+
return static_cast<bool>(lhs) && (!rhs || *lhs > *rhs);
439+
}
440+
441+
/** Checks if the left Optional is less than or equal to the right Optional.
442+
443+
Returns true if the left is disengaged or the right is engaged and the left's value is less or equal.
444+
*/
445+
template <typename T, typename U>
446+
constexpr detail::OptionalLeT<T, U>
447+
operator<=(Optional<T> const& lhs, Optional<U> const& rhs)
448+
{
449+
return !lhs || (static_cast<bool>(rhs) && *lhs <= *rhs);
450+
}
451+
452+
/** Checks if the left Optional is greater than or equal to the right Optional.
453+
454+
Returns true if the right is disengaged or the left is engaged and its value is greater or equal.
455+
*/
456+
template <typename T, typename U>
457+
constexpr detail::OptionalGeT<T, U>
458+
operator>=(Optional<T> const& lhs, Optional<U> const& rhs)
459+
{
460+
return !rhs || (static_cast<bool>(lhs) && *lhs >= *rhs);
461+
}
462+
463+
/** Performs a three-way comparison between two Optional values.
464+
465+
If both are engaged, compares their contained values; otherwise, compares engagement state.
466+
*/
467+
template <typename T, std::three_way_comparable_with<T> U>
468+
[[nodiscard]]
469+
constexpr std::compare_three_way_result_t<T, U>
470+
operator<=>(Optional<T> const& x, Optional<U> const& y)
471+
{
472+
return x && y ? *x <=> *y : bool(x) <=> bool(y);
473+
}
474+
475+
/** Checks if the Optional is disengaged (equal to std::nullopt).
476+
477+
Returns true if the Optional does not contain a value.
478+
*/
479+
template <typename T>
480+
[[nodiscard]]
481+
constexpr bool
482+
operator==(Optional<T> const& lhs, std::nullopt_t) noexcept
483+
{
484+
return !lhs;
485+
}
486+
487+
/** Performs a three-way comparison between an Optional and std::nullopt.
488+
489+
Returns std::strong_ordering::greater if engaged, std::strong_ordering::equal if disengaged.
490+
*/
491+
template <typename T>
492+
[[nodiscard]]
493+
constexpr std::strong_ordering
494+
operator<=>(Optional<T> const& x, std::nullopt_t) noexcept
495+
{
496+
return bool(x) <=> false;
497+
}
498+
499+
/** Compares an engaged Optional to a value for equality.
500+
501+
Returns true if the Optional is engaged and its value equals rhs.
502+
*/
503+
template <typename T, typename U>
504+
requires(!detail::isOptionalV<U>)
505+
constexpr detail::OptionalEqT<T, U>
506+
operator== [[nodiscard]] (Optional<T> const& lhs, U const& rhs)
507+
{
508+
return lhs && *lhs == rhs;
509+
}
510+
511+
/** Compares a value to an engaged Optional for equality.
512+
513+
Returns true if the Optional is engaged and its value equals lhs.
514+
*/
515+
template <typename T, typename U>
516+
requires(!detail::isOptionalV<T>)
517+
constexpr detail::OptionalEqT<T, U>
518+
operator== [[nodiscard]] (T const& lhs, Optional<U> const& rhs)
519+
{
520+
return rhs && lhs == *rhs;
521+
}
522+
523+
/** Compares an Optional to a value for inequality.
524+
525+
Returns true if the Optional is disengaged or its value does not equal rhs.
526+
*/
527+
template <typename T, typename U>
528+
requires(!detail::isOptionalV<U>)
529+
constexpr detail::OptionalNeT<T, U>
530+
operator!= [[nodiscard]] (Optional<T> const& lhs, U const& rhs)
531+
{
532+
return !lhs || *lhs != rhs;
533+
}
534+
535+
/** Compares a value to an Optional for inequality.
536+
537+
Returns true if the Optional is disengaged or its value does not equal lhs.
538+
*/
539+
template <typename T, typename U>
540+
requires(!detail::isOptionalV<T>)
541+
constexpr detail::OptionalNeT<T, U>
542+
operator!= [[nodiscard]] (T const& lhs, Optional<U> const& rhs)
543+
{
544+
return !rhs || lhs != *rhs;
545+
}
546+
547+
/** Checks if the Optional is less than a value.
548+
549+
Returns true if the Optional is disengaged or its value is less than rhs.
550+
*/
551+
template <typename T, typename U>
552+
requires(!detail::isOptionalV<U>)
553+
constexpr detail::OptionalLtT<T, U>
554+
operator<[[nodiscard]] (Optional<T> const& lhs, U const& rhs)
555+
{
556+
return !lhs || *lhs < rhs;
557+
}
558+
559+
/** Checks if a value is less than an engaged Optional.
560+
561+
Returns true if the Optional is engaged and lhs is less than its value.
562+
*/
563+
template <typename T, typename U>
564+
requires(!detail::isOptionalV<T>)
565+
constexpr detail::OptionalLtT<T, U>
566+
operator<[[nodiscard]] (T const& lhs, Optional<U> const& rhs)
567+
{
568+
return rhs && lhs < *rhs;
569+
}
570+
571+
/** Checks if the Optional is greater than a value.
572+
573+
Returns true if the Optional is engaged and its value is greater than rhs.
574+
*/
575+
template <typename T, typename U>
576+
requires(!detail::isOptionalV<U>)
577+
constexpr detail::OptionalGtT<T, U>
578+
operator> [[nodiscard]] (Optional<T> const& lhs, U const& rhs)
579+
{
580+
return lhs && *lhs > rhs;
581+
}
582+
583+
/** Checks if a value is greater than an Optional.
584+
585+
Returns true if the Optional is disengaged or lhs is greater than its value.
586+
*/
587+
template <typename T, typename U>
588+
requires(!detail::isOptionalV<T>)
589+
constexpr detail::OptionalGtT<T, U>
590+
operator> [[nodiscard]] (T const& lhs, Optional<U> const& rhs)
591+
{
592+
return !rhs || lhs > *rhs;
593+
}
594+
595+
/** Checks if the Optional is less than or equal to a value.
596+
597+
Returns true if the Optional is disengaged or its value is less than or equal to rhs.
598+
*/
599+
template <typename T, typename U>
600+
requires(!detail::isOptionalV<U>)
601+
constexpr detail::OptionalLeT<T, U>
602+
operator<= [[nodiscard]] (Optional<T> const& lhs, U const& rhs)
603+
{
604+
return !lhs || *lhs <= rhs;
605+
}
606+
607+
/** Checks if a value is less than or equal to an engaged Optional.
608+
609+
Returns true if the Optional is engaged and lhs is less than or equal to its value.
610+
*/
611+
template <typename T, typename U>
612+
requires(!detail::isOptionalV<T>)
613+
constexpr detail::OptionalLeT<T, U>
614+
operator<= [[nodiscard]] (T const& lhs, Optional<U> const& rhs)
615+
{
616+
return rhs && lhs <= *rhs;
617+
}
618+
619+
/** Checks if the Optional is greater than or equal to a value.
620+
621+
Returns true if the Optional is engaged and its value is greater than or equal to rhs.
622+
*/
623+
template <typename T, typename U>
624+
requires(!detail::isOptionalV<U>)
625+
constexpr detail::OptionalGeT<T, U>
626+
operator>= [[nodiscard]] (Optional<T> const& lhs, U const& rhs)
627+
{
628+
return lhs && *lhs >= rhs;
629+
}
630+
631+
/** Checks if a value is greater than or equal to an Optional.
632+
633+
Returns true if the Optional is disengaged or lhs is greater than or equal to its value.
634+
*/
635+
template <typename T, typename U>
636+
requires(!detail::isOptionalV<T>)
637+
constexpr detail::OptionalGeT<T, U>
638+
operator>= [[nodiscard]] (T const& lhs, Optional<U> const& rhs)
639+
{
640+
return !rhs || lhs >= *rhs;
641+
}
642+
643+
/** Performs a three-way comparison between an Optional and a value.
644+
645+
If the Optional is engaged, compares its value to v; otherwise, returns less.
646+
*/
647+
template <typename T, typename U>
648+
requires(!detail::isDerivedFromOptional<U>)
649+
&& requires { typename std::compare_three_way_result_t<T, U>; }
650+
&& std::three_way_comparable_with<T, U>
651+
constexpr std::compare_three_way_result_t<T, U>
652+
operator<=> [[nodiscard]] (Optional<T> const& x, U const& v)
653+
{
654+
return bool(x) ? *x <=> v : std::strong_ordering::less;
655+
}
342656
} // namespace clang::mrdocs
343657

344658
#endif

0 commit comments

Comments
 (0)