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