Optional.h#

Fully qualified name: carb/cpp/Optional.h

File members: carb/cpp/Optional.h

// SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.

// Implements std::optional from C++17.
// Heavily borrowed from MS STL: https://github.com/microsoft/STL/blob/master/stl/inc/optional

#pragma once

#include "../Defines.h"

#define CARB_IMPLOPTIONAL
#include "detail/ImplOptional.h"
#undef CARB_IMPLOPTIONAL

#include "../../omni/detail/PointerIterator.h"

#include <optional>
#include <type_traits>
#include <utility>

#if CARB_HAS_CPP20
#    include <concepts>
#endif

namespace carb::cpp
{

using nullopt_t = std::nullopt_t;

constexpr nullopt_t nullopt = std::nullopt;

using bad_optional_access = std::bad_optional_access;

template <typename T>
class CARB_VIZ optional : private detail::SelectHierarchy_t<detail::OptionalConstructor<T>, T>
{
    using BaseClass = detail::SelectHierarchy_t<detail::OptionalConstructor<T>, T>;

    static_assert(!std::is_same_v<std::remove_cv_t<T>, nullopt_t> && !std::is_same_v<std::remove_cv_t<T>, std::in_place_t>,
                  "T may not be nullopt_t or inplace_t");
    static_assert(std::is_object_v<T> && std::is_destructible_v<T> && !std::is_array_v<T>,
                  "T does not meet Cpp17Destructible requirements");

    template <typename U>
    constexpr static bool AllowDirectConversion_v =
        !std::is_same_v<remove_cvref_t<U>, optional> && !std::is_same_v<remove_cvref_t<U>, std::in_place_t> &&
        std::is_constructible_v<T, U>;

    template <typename U>
    constexpr static bool AllowUnwrapping_v =
        !(std::is_same_v<T, U> || std::is_constructible_v<T, optional<U>&> ||
          std::is_constructible_v<T, const optional<U>&> || std::is_constructible_v<T, const optional<U>> ||
          std::is_constructible_v<T, optional<U>> || std::is_convertible_v<optional<U>&, T> ||
          std::is_convertible_v<const optional<U>&, T> || std::is_convertible_v<const optional<U>, T> ||
          std::is_convertible_v<optional<U>, T>);

    template <typename U>
    constexpr static bool AllowUnwrappingAssignment_v =
        !(std::is_same_v<T, U> || std::is_assignable_v<T&, optional<U>&> || std::is_assignable_v<T&, const optional<U>&> ||
          std::is_assignable_v<T&, const optional<U>> || std::is_assignable_v<T&, optional<U>>);

    [[noreturn]] static void onBadAccess()
    {
#if CARB_EXCEPTIONS_ENABLED
        throw bad_optional_access();
#else
        CARB_FATAL_UNLESS(0, "bad optional access");
#endif
    }

    template <typename F, typename U>
    constexpr optional(detail::ConstructFromInvokeResult tag,
                       F&& f,
                       U&& arg) noexcept(noexcept(static_cast<T>(std::invoke(std::forward<F>(f), std::forward<U>(arg)))))
        : BaseClass(tag, std::forward<F>(f), std::forward<U>(arg))
    {
    }

    template <typename U>
    friend class optional;

public:
    using value_type = T;

    using iterator = omni::detail::PointerIterator<value_type*, optional<value_type>>;

    using const_iterator = omni::detail::PointerIterator<const value_type*, optional<value_type>>;

    constexpr optional() noexcept
    {
    }
    constexpr optional(nullopt_t) noexcept
    {
    }

    optional(const optional& other) : BaseClass(static_cast<const BaseClass&>(other))
    {
    }

    optional(optional&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
        : BaseClass(static_cast<BaseClass&&>(std::move(other)))
    {
    }

    optional& operator=(const optional& other)
    {
        if (other)
            this->assign(*other);
        else
            reset();
        return *this;
    }

    optional& operator=(optional&& other) noexcept(
        std::is_nothrow_move_assignable_v<T>&& std::is_nothrow_move_constructible_v<T>)
    {
        if (other)
            this->assign(std::move(*other));
        else
            reset();
        return *this;
    }

    // The spec states that this is conditionally-explicit, which is a C++20 feature, so we have to work around it by
    // having two functions with SFINAE
    template <typename U,
              std::enable_if_t<AllowUnwrapping_v<U> && std::is_constructible_v<T, const U&> && std::is_convertible_v<const U&, T>,
                               int> = 0>
    optional(const optional<U>& other)
    {
        if (other)
            this->construct(*other);
    }
#ifndef DOXYGEN_BUILD
    // Note: this constructor is excluded from the docs build because the `rebreather` tool
    //       sees this constructor as a duplicate of the one above and generates an error.
    template <
        typename U,
        std::enable_if_t<AllowUnwrapping_v<U> && std::is_constructible_v<T, const U&> && !std::is_convertible_v<const U&, T>,
                         int> = 0>
    explicit optional(const optional<U>& other)
    {
        if (other)
            this->construct(*other);
    }
#endif

    // The spec states that this is conditionally-explicit, which is a C++20 feature, so we have to work around it by
    // having two functions with SFINAE
    template <typename U,
              std::enable_if_t<AllowUnwrapping_v<U> && std::is_constructible_v<T, U> && std::is_convertible_v<U, T>, int> = 0>
    optional(optional<U>&& other)
    {
        if (other)
            this->construct(std::move(*other));
    }
#ifndef DOXYGEN_BUILD
    // Note: this constructor is excluded from the docs build because the `rebreather` tool
    //       sees this constructor as a duplicate of the one above and generates an error.
    template <typename U,
              std::enable_if_t<AllowUnwrapping_v<U> && std::is_constructible_v<T, U> && !std::is_convertible_v<U, T>, int> = 0>
    explicit optional(optional<U>&& other)
    {
        if (other)
            this->construct(std::move(*other));
    }
#endif

    template <class... Args, std::enable_if_t<std::is_constructible_v<T, Args...>, int> = 0>
    optional(std::in_place_t, Args&&... args) : BaseClass(std::in_place, std::forward<Args>(args)...)
    {
    }

    template <typename U, class... Args, std::enable_if_t<std::is_constructible_v<T, std::initializer_list<U>&, Args...>, int> = 0>
    optional(std::in_place_t, std::initializer_list<U> ilist, Args&&... args)
        : BaseClass(std::in_place, ilist, std::forward<Args>(args)...)
    {
    }

    // The spec states that this is conditionally-explicit, which is a C++20 feature, so we have to work around it by
    // having two functions with SFINAE
    template <typename U = value_type, std::enable_if_t<AllowDirectConversion_v<U> && std::is_convertible_v<U, T>, int> = 0>
    constexpr optional(U&& value) : BaseClass(std::in_place, std::forward<U>(value))
    {
    }
#ifndef DOXYGEN_BUILD
    // Note: this constructor is excluded from the docs build because the `rebreather` tool
    //       sees this constructor as a duplicate of the one above and generates an error.
    template <typename U = value_type, std::enable_if_t<AllowDirectConversion_v<U> && !std::is_convertible_v<U, T>, int> = 0>
    constexpr explicit optional(U&& value) : BaseClass(std::in_place, std::forward<U>(value))
    {
    }
#endif

    ~optional() = default;

    optional& operator=(nullopt_t) noexcept
    {
        reset();
        return *this;
    }

    template <typename U = value_type,
              std::enable_if_t<!std::is_same_v<optional, remove_cvref_t<U>> &&
                                   !(std::is_scalar_v<T> && std::is_same_v<T, std::decay_t<U>>)&&std::is_constructible_v<T, U> &&
                                   std::is_assignable_v<T&, U>,
                               int> = 0>
    optional& operator=(U&& value)
    {
        this->assign(std::forward<U>(value));
        return *this;
    }

    template <typename U,
              std::enable_if_t<AllowUnwrappingAssignment_v<U> && std::is_constructible_v<T, const U&> &&
                                   std::is_assignable_v<T&, const U&>,
                               int> = 0>
    optional& operator=(const optional<U>& other)
    {
        if (other)
            this->assign(*other);
        else
            reset();
        return *this;
    }

    template <typename U,
              std::enable_if_t<AllowUnwrappingAssignment_v<U> && std::is_constructible_v<T, U> && std::is_assignable_v<T&, U>,
                               int> = 0>
    optional& operator=(optional<U>&& other)
    {
        if (other)
            this->assign(std::move(*other));
        else
            reset();
        return *this;
    }

    iterator begin() noexcept
    {
        auto pthis = static_cast<BaseClass*>(this);
        return iterator(std::addressof(pthis->value));
    }
    const_iterator begin() const noexcept
    {
        auto pthis = static_cast<const BaseClass*>(this);
        return const_iterator(std::addressof(pthis->value));
    }

    iterator end() noexcept
    {
        auto pthis = static_cast<BaseClass*>(this);
        return iterator(std::addressof(pthis->value) + pthis->hasValue);
    }
    const_iterator end() const noexcept
    {
        auto pthis = static_cast<const BaseClass*>(this);
        return const_iterator(std::addressof(pthis->value) + pthis->hasValue);
    }

    constexpr const T* operator->() const
    {
        return std::addressof(this->val());
    }
    constexpr T* operator->()
    {
        return std::addressof(this->val());
    }

    constexpr const T& operator*() const&
    {
        return this->val();
    }

    constexpr T& operator*() &
    {
        return this->val();
    }

    constexpr const T&& operator*() const&&
    {
        return std::move(this->val());
    }

    constexpr T&& operator*() &&
    {
        return std::move(this->val());
    }

    constexpr explicit operator bool() const noexcept
    {
        return this->hasValue;
    }
    constexpr bool has_value() const noexcept
    {
        return this->hasValue;
    }

    constexpr const T& value() const&
    {
        if (!this->hasValue)
            onBadAccess();
        return this->val();
    }

    constexpr T& value() &
    {
        if (!this->hasValue)
            onBadAccess();
        return this->val();
    }

    constexpr const T&& value() const&&
    {
        if (!this->hasValue)
            onBadAccess();
        return std::move(this->val());
    }

    constexpr T&& value() &&
    {
        if (!this->hasValue)
            onBadAccess();
        return std::move(this->val());
    }

    template <typename U>
    constexpr std::remove_cv_t<T> value_or(U&& default_value) const&
    {
        static_assert(
            std::is_convertible_v<const T&, std::remove_cv_t<T>>,
            "The const overload of optional<T>::value_or() requires const T& to be convertible to std::remove_cv_t<T>");
        static_assert(std::is_convertible_v<U, T>, "optional<T>::value_or() requires U to be convertible to T");

        if (this->hasValue)
            return this->val();

        return static_cast<typename std::remove_cv_t<T>>(std::forward<U>(default_value));
    }

    template <typename U>
    constexpr typename std::remove_cv_t<T> value_or(U&& default_value) &&
    {
        static_assert(
            std::is_convertible_v<T, std::remove_cv_t<T>>,
            "The rvalue overload of optional<T>::value_or() requires T to be convertible to std::remove_cv_t<T>");
        static_assert(std::is_convertible_v<U, T>, "optional<T>::value_or() requires U to be convertible to T");

        if (this->hasValue)
            return this->val();

        return static_cast<typename std::remove_cv_t<T>>(std::forward<U>(default_value));
    }

    template <typename F>
    constexpr auto and_then(F&& f) &
    {
        using Ret = invoke_result_t<F, value_type&>;

        static_assert(detail::is_specialization_v<remove_cvref_t<Ret>, optional>,
                      "optional<T>::and_then(F) requires the return type of F to be a specialization of optional "
                      "(N4950 [optional.monadic]/2).");

        if (this->hasValue)
            return std::invoke(std::forward<F>(f), static_cast<value_type&>(this->val()));
        else
            return remove_cvref_t<Ret>{};
    }

    template <typename F>
    constexpr auto and_then(F&& f) const&
    {
        using Ret = invoke_result_t<F, const value_type&>;

        static_assert(detail::is_specialization_v<remove_cvref_t<Ret>, optional>,
                      "optional<T>::and_then(F) requires the return type of F to be a specialization of optional "
                      "(N4950 [optional.monadic]/2).");

        if (this->hasValue)
            return std::invoke(std::forward<F>(f), static_cast<const value_type&>(this->val()));
        else
            return remove_cvref_t<Ret>{};
    }

    template <typename F>
    constexpr auto and_then(F&& f) &&
    {
        using Ret = invoke_result_t<F, value_type>;

        static_assert(detail::is_specialization_v<remove_cvref_t<Ret>, optional>,
                      "optional<T>::and_then(F) requires the return type of F to be a specialization of optional "
                      "(N4950 [optional.monadic]/2).");

        if (this->hasValue)
            return std::invoke(std::forward<F>(f), static_cast<T&&>(this->val()));
        else
            return remove_cvref_t<Ret>{};
    }

    template <typename F>
    constexpr auto and_then(F&& f) const&&
    {
        using Ret = invoke_result_t<F, const value_type>;

        static_assert(detail::is_specialization_v<remove_cvref_t<Ret>, optional>,
                      "optional<T>::and_then(F) requires the return type of F to be a specialization of optional "
                      "(N4950 [optional.monadic]/2).");

        if (this->hasValue)
            return std::invoke(std::forward<F>(f), static_cast<const T&&>(this->val()));
        else
            return remove_cvref_t<Ret>{};
    }

    template <typename F>
    constexpr auto transform(F&& f) &
    {
        using Ret = std::remove_cv_t<invoke_result_t<F, value_type&>>;

        static_assert(
            !detail::is_any_of_v<Ret, nullopt_t, std::in_place_t>,
            "optional<T>::transform(F) requires the return type of F to be a type other than nullopt_t or in_place_t "
            "(N4950 [optional.monadic]/8).");
        static_assert(std::is_object_v<Ret> && !std::is_array_v<Ret>,
                      "optional<T>::transform(F) requires the return type of F to be a non-array object type "
                      "(N4950 [optional.monadic]/8).");

        if (this->hasValue)
            return optional<Ret>{ detail::ConstructFromInvokeResult{}, std::forward<F>(f),
                                  static_cast<value_type&>(this->val()) };
        else
            return optional<Ret>{};
    }

    template <typename F>
    constexpr auto transform(F&& f) const&
    {
        using Ret = std::remove_cv_t<invoke_result_t<F, const value_type&>>;

        static_assert(
            !detail::is_any_of_v<Ret, nullopt_t, std::in_place_t>,
            "optional<T>::transform(F) requires the return type of F to be a type other than nullopt_t or in_place_t "
            "(N4950 [optional.monadic]/8).");
        static_assert(std::is_object_v<Ret> && !std::is_array_v<Ret>,
                      "optional<T>::transform(F) requires the return type of F to be a non-array object type "
                      "(N4950 [optional.monadic]/8).");

        if (this->hasValue)
            return optional<Ret>{ detail::ConstructFromInvokeResult{}, std::forward<F>(f),
                                  static_cast<const value_type&>(this->val()) };
        else
            return optional<Ret>{};
    }

    template <typename F>
    constexpr auto transform(F&& f) &&
    {
        using Ret = std::remove_cv_t<invoke_result_t<F, value_type>>;

        static_assert(
            !detail::is_any_of_v<Ret, nullopt_t, std::in_place_t>,
            "optional<T>::transform(F) requires the return type of F to be a type other than nullopt_t or in_place_t "
            "(N4950 [optional.monadic]/8).");
        static_assert(std::is_object_v<Ret> && !std::is_array_v<Ret>,
                      "optional<T>::transform(F) requires the return type of F to be a non-array object type "
                      "(N4950 [optional.monadic]/8).");

        if (this->hasValue)
            return optional<Ret>{ detail::ConstructFromInvokeResult{}, std::forward<F>(f), static_cast<T&&>(this->val()) };
        else
            return optional<Ret>{};
    }

    template <typename F>
    constexpr auto transform(F&& f) const&&
    {
        using Ret = std::remove_cv_t<invoke_result_t<F, const value_type>>;

        static_assert(
            !detail::is_any_of_v<Ret, nullopt_t, std::in_place_t>,
            "optional<T>::transform(F) requires the return type of F to be a type other than nullopt_t or in_place_t "
            "(N4950 [optional.monadic]/8).");
        static_assert(std::is_object_v<Ret> && !std::is_array_v<Ret>,
                      "optional<T>::transform(F) requires the return type of F to be a non-array object type "
                      "(N4950 [optional.monadic]/8).");

        if (this->hasValue)
            return optional<Ret>{ detail::ConstructFromInvokeResult{}, std::forward<F>(f),
                                  static_cast<const T&&>(this->val()) };
        else
            return optional<Ret>{};
    }

#if CARB_HAS_CPP20 && !defined(DOXYGEN_BUILD)
    template <std::invocable<> F>
    requires std::copy_constructible<T>
#else
    template <typename F, typename U = T, std::enable_if_t<std::is_invocable_v<F> && std::is_copy_constructible_v<U>, int> = 0>
#endif
    constexpr optional or_else(F&& f) const&
    {
        static_assert(std::is_same_v<remove_cvref_t<invoke_result_t<F>>, optional>,
                      "optional<T>::or_else(F) requires F to return an optional<T> (N4950 [optional.monadic]/14).");

        if (this->hasValue)
            return *this;
        else
            return std::forward<F>(f)();
    }

#if CARB_HAS_CPP20 && !defined(DOXYGEN_BUILD)
    template <std::invocable<> F>
    requires std::move_constructible<T>
#else
    template <typename F, typename U = T, std::enable_if_t<std::is_invocable_v<F> && std::is_move_constructible_v<U>, int> = 0>
#endif
    constexpr optional or_else(F&& f) &&
    {
        static_assert(std::is_same_v<remove_cvref_t<invoke_result_t<F>>, optional>,
                      "optional<T>::or_else(F) requires F to return an optional<T> (N4950 [optional.monadic]/14).");

        if (this->hasValue)
            return std::move(*this);
        else
            return std::forward<F>(f)();
    }

    void swap(optional& other) noexcept(std::is_nothrow_move_constructible_v<T>&& std::is_nothrow_swappable_v<T>)
    {
        static_assert(std::is_move_constructible_v<T>, "T must be move constructible");
        static_assert(std::is_swappable_v<T>, "T must be swappable");

        const bool engaged = this->hasValue;
        if (engaged == other.hasValue)
        {
            if (engaged)
            {
                using std::swap; // Enable ADL
                swap(**this, *other);
            }
        }
        else
        {
            optional& source = engaged ? *this : other;
            optional& target = engaged ? other : *this;
            target.construct(std::move(*source));
            source.reset();
        }
    }

    void reset() noexcept
    {
        BaseClass::reset();
    }

    template <class... Args>
    T& emplace(Args&&... args)
    {
        reset();
        return this->construct(std::forward<Args>(args)...);
    }
    template <typename U, class... Args, std::enable_if_t<std::is_constructible_v<T, std::initializer_list<U>&, Args...>, int> = 0>
    T& emplace(std::initializer_list<U> ilist, Args&&... args)
    {
        reset();
        return this->construct(ilist, std::forward<Args>(args)...);
    }
};

#ifndef DOXYGEN_BUILD
template <typename T>
optional(T) -> optional<T>;

template <typename T, typename U>
constexpr bool operator==(const optional<T>& lhs, const optional<U>& rhs)
{
    const bool lhv = lhs.has_value();
    return lhv == rhs.has_value() && (!lhv || *lhs == *rhs);
}
template <typename T, typename U>
constexpr bool operator!=(const optional<T>& lhs, const optional<U>& rhs)
{
    const bool lhv = lhs.has_value();
    return lhv != rhs.has_value() || (lhv && *lhs != *rhs);
}
template <typename T, typename U>
constexpr bool operator<(const optional<T>& lhs, const optional<U>& rhs)
{
    return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs);
}
template <typename T, typename U>
constexpr bool operator<=(const optional<T>& lhs, const optional<U>& rhs)
{
    return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs);
}
template <typename T, typename U>
constexpr bool operator>(const optional<T>& lhs, const optional<U>& rhs)
{
    return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs);
}
template <typename T, typename U>
constexpr bool operator>=(const optional<T>& lhs, const optional<U>& rhs)
{
    return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs);
}

template <typename T>
constexpr bool operator==(const optional<T>& opt, nullopt_t) noexcept
{
    return !opt.has_value();
}
template <typename T>
constexpr bool operator==(nullopt_t, const optional<T>& opt) noexcept
{
    return !opt.has_value();
}
template <typename T>
constexpr bool operator!=(const optional<T>& opt, nullopt_t) noexcept
{
    return opt.has_value();
}
template <typename T>
constexpr bool operator!=(nullopt_t, const optional<T>& opt) noexcept
{
    return opt.has_value();
}
template <typename T>
constexpr bool operator<(const optional<T>&, nullopt_t) noexcept
{
    return false;
}
template <typename T>
constexpr bool operator<(nullopt_t, const optional<T>& opt) noexcept
{
    return opt.has_value();
}
template <typename T>
constexpr bool operator<=(const optional<T>& opt, nullopt_t) noexcept
{
    return !opt.has_value();
}
template <typename T>
constexpr bool operator<=(nullopt_t, const optional<T>&) noexcept
{
    return true;
}
template <typename T>
constexpr bool operator>(const optional<T>& opt, nullopt_t) noexcept
{
    return opt.has_value();
}
template <typename T>
constexpr bool operator>(nullopt_t, const optional<T>&) noexcept
{
    return false;
}
template <typename T>
constexpr bool operator>=(const optional<T>&, nullopt_t) noexcept
{
    return true;
}
template <typename T>
constexpr bool operator>=(nullopt_t, const optional<T>& opt) noexcept
{
    return !opt.has_value();
}

template <typename T, typename U, detail::EnableIfComparableWithEqual<T, U> = 0>
constexpr bool operator==(const optional<T>& opt, const U& value)
{
    return opt ? *opt == value : false;
}
template <typename T, typename U, detail::EnableIfComparableWithEqual<T, U> = 0>
constexpr bool operator==(const T& value, const optional<U>& opt)
{
    return opt ? *opt == value : false;
}
template <typename T, typename U, detail::EnableIfComparableWithNotEqual<T, U> = 0>
constexpr bool operator!=(const optional<T>& opt, const U& value)
{
    return opt ? *opt != value : true;
}
template <typename T, typename U, detail::EnableIfComparableWithNotEqual<T, U> = 0>
constexpr bool operator!=(const T& value, const optional<U>& opt)
{
    return opt ? *opt != value : true;
}
template <typename T, typename U, detail::EnableIfComparableWithLess<T, U> = 0>
constexpr bool operator<(const optional<T>& opt, const U& value)
{
    return opt ? *opt < value : true;
}
template <typename T, typename U, detail::EnableIfComparableWithLess<T, U> = 0>
constexpr bool operator<(const T& value, const optional<U>& opt)
{
    return opt ? value < *opt : false;
}
template <typename T, typename U, detail::EnableIfComparableWithLessEqual<T, U> = 0>
constexpr bool operator<=(const optional<T>& opt, const U& value)
{
    return opt ? *opt <= value : true;
}
template <typename T, typename U, detail::EnableIfComparableWithLessEqual<T, U> = 0>
constexpr bool operator<=(const T& value, const optional<U>& opt)
{
    return opt ? value <= *opt : false;
}
template <typename T, typename U, detail::EnableIfComparableWithGreater<T, U> = 0>
constexpr bool operator>(const optional<T>& opt, const U& value)
{
    return opt ? *opt > value : false;
}
template <typename T, typename U, detail::EnableIfComparableWithGreater<T, U> = 0>
constexpr bool operator>(const T& value, const optional<U>& opt)
{
    return opt ? value > *opt : true;
}
template <typename T, typename U, detail::EnableIfComparableWithGreaterEqual<T, U> = 0>
constexpr bool operator>=(const optional<T>& opt, const U& value)
{
    return opt ? *opt >= value : false;
}
template <typename T, typename U, detail::EnableIfComparableWithGreaterEqual<T, U> = 0>
constexpr bool operator>=(const T& value, const optional<U>& opt)
{
    return opt ? value >= *opt : true;
}

template <typename T, std::enable_if_t<std::is_move_constructible_v<T> && std::is_swappable_v<T>, int> = 0>
void swap(optional<T>& lhs, optional<T>& rhs) noexcept(noexcept(lhs.swap(rhs)))
{
    lhs.swap(rhs);
}

template <typename T>
constexpr optional<std::decay_t<T>> make_optional(T&& value)
{
    return optional<std::decay_t<T>>{ std::forward<T>(value) };
}
template <typename T, class... Args>
constexpr optional<T> make_optional(Args&&... args)
{
    return optional<T>{ std::in_place, std::forward<Args>(args)... };
}
template <typename T, typename U, class... Args>
constexpr optional<T> make_optional(std::initializer_list<U> il, Args&&... args)
{
    return optional<T>{ std::in_place, il, std::forward<Args>(args)... };
}
#endif

} // namespace carb::cpp

#if CARB_HAS_CPP20 && !defined(DOXYGEN_BUILD)
#    include <ranges>
template <class T>
constexpr bool std::ranges::enable_view<carb::cpp::optional<T>> = true;
#endif