AUI Framework  master
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
AOptional.h
    1/*
    2 * AUI Framework - Declarative UI toolkit for modern C++20
    3 * Copyright (C) 2020-2025 Alex2772 and Contributors
    4 *
    5 * SPDX-License-Identifier: MPL-2.0
    6 *
    7 * This Source Code Form is subject to the terms of the Mozilla Public
    8 * License, v. 2.0. If a copy of the MPL was not distributed with this
    9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   10 */
   11
   12#pragma once
   13
   14#include <cstdint>
   15#include <cassert>
   16#include <utility>
   17#include <optional>
   18#include <fstream>
   19#include <stdexcept>
   20#include "AUI/Traits/concepts.h"
   21#include <AUI/Core.h>
   22
   23
   24namespace aui::impl::optional {
   25    API_AUI_CORE void throwException(const char* message);
   26}
   27
   32template<typename T>
   33class AOptional {
   34public:
   35    constexpr AOptional() noexcept = default;
   36    constexpr AOptional(std::nullopt_t) noexcept {}
   37
   38
   39    template<typename U = T,
   40             std::enable_if_t<std::is_constructible_v<T, U> && std::is_convertible_v<U&&, T>, bool> = true>
   41    constexpr AOptional(U&& rhs) noexcept: mInitialized(true) {
   42        new (ptrUnsafe()) T(std::forward<U>(rhs));
   43    }
   44
   45    template<typename U = T,
   46             std::enable_if_t<std::is_constructible_v<T, U> && !std::is_convertible_v<U&&, T>, bool> = true>
   47    explicit constexpr AOptional(U&& rhs) noexcept: mInitialized(true) {
   48        new (ptrUnsafe()) T(std::forward<U>(rhs));
   49    }
   50
   51    constexpr AOptional(const AOptional& rhs) {
   52        operator=(rhs);
   53    }
   54
   55    constexpr AOptional(AOptional&& rhs) noexcept {
   56        operator=(std::move(rhs));
   57    }
   58
   59    template<typename U>
   60    constexpr AOptional(const AOptional<U>& rhs) {
   61        operator=(rhs);
   62    }
   63
   64    template<typename U>
   65    constexpr AOptional(AOptional<U>&& rhs) noexcept {
   66        operator=(std::move(rhs));
   67    }
   68
   69    ~AOptional() {
   70        if (mInitialized) ptrUnsafe()->~T();
   71    }
   72
   73
   74    [[nodiscard]]
   75    bool hasValue() const noexcept {
   76        return mInitialized;
   77    }
   78
   79    constexpr explicit operator bool() const noexcept {
   80        return hasValue();
   81    }
   82
   83    template<typename... Args>
   84    constexpr AOptional<T>& emplace(Args&&... args) {
   85        reset();
   86        new (ptrUnsafe()) T(std::forward<Args>(args)...);
   87        mInitialized = true;
   88        return *this;
   89    }
   90
   91    constexpr AOptional<T>& operator=(std::nullopt_t) noexcept {
   92        reset();
   93        return *this;
   94    }
   95
   96    template<typename U = T, typename std::enable_if_t<std::is_convertible_v<U&&, T>, bool> = true>
   97    constexpr AOptional<T>& operator=(U&& rhs) noexcept {
   98        reset();
   99        new (ptrUnsafe()) T(std::forward<U>(rhs));
  100        mInitialized = true;
  101        return *this;
  102    }
  103
  104    constexpr AOptional<T>& operator=(const AOptional& rhs) noexcept {
  105        if (rhs) {
  106            operator=(rhs.value());
  107        } else {
  108            reset();
  109        }
  110        return *this;
  111    }
  112
  113    constexpr AOptional<T>& operator=(AOptional&& rhs) noexcept {
  114        if (rhs) {
  115            operator=(std::move(rhs.value()));
  116            rhs.reset();
  117        } else {
  118            reset();
  119        }
  120        return *this;
  121    }
  122
  123    template<typename U>
  124    constexpr AOptional<T>& operator=(const AOptional<U>& rhs) noexcept {
  125        if (rhs) {
  126            operator=(rhs.value());
  127        } else {
  128            reset();
  129        }
  130        return *this;
  131    }
  132
  133    // we want to move the U value, not the whole optional
  134    // NOLINTBEGIN(cppcoreguidelines-rvalue-reference-param-not-moved)
  135    template<typename U>
  136    constexpr AOptional<T>& operator=(AOptional<U>&& rhs) noexcept {
  137        if (rhs) {
  138            operator=(std::move(rhs.value()));
  139            rhs.reset();
  140            return *this;
  141        } else {
  142            reset();
  143        }
  144        return *this;
  145    }
  146    //NOLINTEND(cppcoreguidelines-rvalue-reference-param-not-moved)
  147
  148    template<typename U = T>
  149    constexpr AOptional<T>& operator=(T&& rhs) noexcept {
  150        reset();
  151        new (ptrUnsafe()) T(std::move(rhs));
  152        mInitialized = true;
  153        return *this;
  154    }
  155
  156    [[nodiscard]]
  157    T& value() noexcept {
  158        AUI_ASSERTX(mInitialized, "optional is empty");
  159        return reinterpret_cast<T&>(mStorage);
  160    }
  161
  162    [[nodiscard]]
  163    const T& value() const noexcept {
  164        AUI_ASSERTX(mInitialized, "optional is empty");
  165        return reinterpret_cast<const T&>(mStorage);
  166    }
  167
  168    [[nodiscard]]
  169    T* ptr() noexcept {
  170        return &value();
  171    }
  172
  173    [[nodiscard]]
  174    const T* ptr() const noexcept {
  175        return &value();
  176    }
  177
  178    [[nodiscard]]
  179    T* operator->() noexcept {
  180        return ptr();
  181    }
  182
  183    [[nodiscard]]
  184    const T* operator->() const noexcept {
  185        return ptr();
  186    }
  187
  188    [[nodiscard]]
  189    T& operator*() noexcept {
  190        return value();
  191    }
  192
  193    [[nodiscard]]
  194    const T& operator*() const noexcept {
  195        return value();
  196    }
  197
  198    void reset() noexcept {
  199        if (mInitialized) {
  200            ptrUnsafe()->~T();
  201            mInitialized = false;
  202        }
  203    }
  204
  208    T& valueOrException(const char* message = "empty optional") {
  209        if (mInitialized) {
  210            return value();
  211        }
  212        aui::impl::optional::throwException(message);
  213        throw std::logic_error("should not have reached here"); // silence "not all control paths return a value" warning
  214    }
  215
  219    const T& valueOrException(const char* message = "empty optional") const {
  220        if (mInitialized) {
  221            return value();
  222        }
  223        aui::impl::optional::throwException(message);
  224        throw std::logic_error("should not have reached here"); // silence "not all control paths return a value" warning
  225    }
  226
  233    template<typename F>
  234    T valueOr(F&& alternative) const {
  235        if (mInitialized) {
  236            return value();
  237        }
  238        constexpr bool isSame = std::is_constructible_v<T, F>;
  239        constexpr bool isInvocable = std::is_invocable_v<F>;
  240
  241        static_assert(isSame || isInvocable, "F is neither same as T nor invokable returning T nor invokable throwing a exception");
  242
  243        if constexpr (isSame) {
  244            return std::forward<F>(alternative);
  245        } else if constexpr(isInvocable) {
  246            if constexpr (std::is_same_v<std::invoke_result_t<F>, void>) {
  247                alternative();
  248                AUI_ASSERT_NO_CONDITION("should not have reached here");
  249                throw std::runtime_error("should not have reached here"); // stub exception
  250            } else {
  251                return alternative();
  252            }
  253        }
  254    }
  255
  256    template<typename U>
  257    [[nodiscard]]
  258    bool operator==(const AOptional<U>& rhs) const noexcept {
  259        return (mInitialized == rhs.mInitialized) && (!mInitialized || value() == rhs.value());
  260    }
  261
  262    template<typename U>
  263    [[nodiscard]]
  264    bool operator==(const U& rhs) const noexcept {
  265        return mInitialized && value() == rhs;
  266    }
  267
  268    [[nodiscard]]
  269    bool operator==(const std::nullopt_t& rhs) const noexcept {
  270        return !mInitialized;
  271    }
  272
  278    template<aui::invocable<const T&> Mapper>
  279    [[nodiscard]]
  280    auto map(Mapper&& mapper) -> AOptional<decltype(std::invoke(std::forward<Mapper>(mapper), value()))> const {
  281        if (hasValue()) {
  282            return std::invoke(std::forward<Mapper>(mapper), value());
  283        }
  284        return std::nullopt;
  285    }
  286
  287private:
  288    std::aligned_storage_t<sizeof(T), alignof(T)> mStorage{};
  289    bool mInitialized = false;
  290
  291    [[nodiscard]]
  292    T* ptrUnsafe() noexcept {
  293        return &valueUnsafe();
  294    }
  295
  296    [[nodiscard]]
  297    T& valueUnsafe() noexcept {
  298        return reinterpret_cast<T&>(mStorage);
  299    }
  300
  301};
  302
  303template<typename T>
  304inline std::ostream& operator<<(std::ostream& o, const AOptional<T>& v) {
  305    if (!v) {
  306        o << "[empty]";
  307    } else {
  308        o << *v;
  309    }
  310    return o;
  311}
  312
Utility wrapper implementing the stack-allocated (fast) optional idiom.
Definition AOptional.h:33
const T & valueOrException(const char *message="empty optional") const
value or exception
Definition AOptional.h:219
T valueOr(F &&alternative) const
value or alternative (either value or callback)
Definition AOptional.h:234
T & valueOrException(const char *message="empty optional")
value or exception
Definition AOptional.h:208
auto map(Mapper &&mapper) -> AOptional< decltype(std::invoke(std::forward< Mapper >(mapper), value()))> const
If a value is present, apply the provided mapper function to it.
Definition AOptional.h:280
#define AUI_ASSERT_NO_CONDITION(what)
Always triggers assertion fail.
Definition Assert.h:94
#define AUI_ASSERTX(condition, what)
Asserts that the passed condition evaluates to true. Adds extra message string.
Definition Assert.h:74