From b3fb243086f752d5358cb072f986ec410e86a3c8 Mon Sep 17 00:00:00 2001 From: Bruno Nicoletti Date: Fri, 1 Dec 2023 17:49:16 +0000 Subject: [PATCH] Changes to be able to build to webassembly with emscripten. (#201) * Changes to enable libcoro to be built with emscripten * Removed the TL::expected dependency from submodules * Tweaked a size test in test_task.cpp that failed in wasm as it em++ seems to be adding padding. --- .githooks/readme-template.md | 4 +- .github/workflows/ci.yml | 49 +- .gitmodules | 7 +- CMakeLists.txt | 31 +- examples/CMakeLists.txt | 4 +- include/coro/detail/tl_expected.hpp | 2655 +++++++++++++++++++++++++++ include/coro/expected.hpp | 2 +- include/coro/semaphore.hpp | 2 +- include/coro/thread_pool.hpp | 2 - src/thread_pool.cpp | 2 +- test/test_task.cpp | 6 +- vendor/tartanllama/expected | 1 - 12 files changed, 2734 insertions(+), 31 deletions(-) create mode 100644 include/coro/detail/tl_expected.hpp delete mode 160000 vendor/tartanllama/expected diff --git a/.githooks/readme-template.md b/.githooks/readme-template.md index b4eed5e9..47891543 100644 --- a/.githooks/readme-template.md +++ b/.githooks/readme-template.md @@ -321,7 +321,7 @@ Completed 10000000 requests Finished 10000000 requests -Server Software: +Server Software: Server Hostname: 127.0.0.1 Server Port: 8888 @@ -412,8 +412,6 @@ This project uses git submodules, to properly checkout this project use: This project depends on the following git sub-modules: * [libc-ares](https://github.com/c-ares/c-ares) For async DNS resolver, this is a git submodule. * [catch2](https://github.com/catchorg/Catch2) For testing, this is embedded in the `test/` directory. - * [expected](https://github.com/TartanLlama/expected) For results on operations that can fail, this is a git submodule in the `vendor/` directory. - * `-std=c++23` if it supports `__cpp_lib_expected` will use `std::expected` instead. #### Building mkdir Release && cd Release diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1220d581..56ef7f43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,7 +165,6 @@ jobs: run: | cd build-release-clang++-feature-networking-disabled ctest -VV - build-fedora-32-to-37: name: fedora-${{ matrix.fedora_version }} runs-on: ubuntu-latest @@ -281,4 +280,50 @@ jobs: - name: test-release-cl run: | cd build-release-cl - ctest --build-config Release -VV + build-emscripten-3_1_45: + name: emscripten-3_1_45 + runs-on: ubuntu-latest + container: + image: ubuntu:22.04 + env: + TZ: America/New_York + DEBIAN_FRONTEND: noninteractive + steps: + - name: apt + run: | + apt-get update + apt-get -y upgrade + apt install -y build-essential software-properties-common + add-apt-repository ppa:ubuntu-toolchain-r/test + apt-get install -y \ + cmake \ + git \ + ninja-build + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + - name: install-emsdk + run: | + git clone https://github.com/emscripten-core/emsdk.git + cd emsdk + ./emsdk install 3.1.45 + ./emsdk activate 3.1.45 + - name: build-release-emscripten + run: | + cd emsdk + . ./emsdk_env.sh + cd .. + mkdir build-release-emscripten + cd build-release-emscripten + emcmake cmake \ + -GNinja \ + -DCMAKE_BUILD_TYPE=Release \ + .. + ninja + - name: test-release-emscripten + run: | + cd emsdk + . ./emsdk_env.sh + cd ../build-release-emscripten + node --experimental-wasm-eh ./test/libcoro_test.js diff --git a/.gitmodules b/.gitmodules index aa4c349f..4bfa2051 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "vendor/c-ares/c-ares"] - path = vendor/c-ares/c-ares - url = https://github.com/c-ares/c-ares.git -[submodule "vendor/tartanllama/expected"] - path = vendor/tartanllama/expected - url = https://github.com/jstranik/expected.git + path = vendor/c-ares/c-ares + url = https://github.com/c-ares/c-ares.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 18442475..67e14fd6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,9 +23,10 @@ option(LIBCORO_EXTERNAL_DEPENDENCIES "Use Cmake find_package to resolve dependen option(LIBCORO_BUILD_TESTS "Build the tests, Default=ON." ON) option(LIBCORO_CODE_COVERAGE "Enable code coverage, tests must also be enabled, Default=OFF" OFF) option(LIBCORO_BUILD_EXAMPLES "Build the examples, Default=ON." ON) -cmake_dependent_option(LIBCORO_FEATURE_PLATFORM "Include linux platform features, Default=ON." ON "NOT MSVC" OFF) -cmake_dependent_option(LIBCORO_FEATURE_NETWORKING "Include networking features, Default=ON." ON "NOT MSVC" OFF) -cmake_dependent_option(LIBCORO_FEATURE_SSL "Include SSL encryption features, Default=ON." ON "NOT MSVC" OFF) + +cmake_dependent_option(LIBCORO_FEATURE_PLATFORM "Include linux platform features, Default=ON." ON "NOT EMSCRIPTEN; NOT MSVC" OFF) +cmake_dependent_option(LIBCORO_FEATURE_NETWORKING "Include networking features, Default=ON." ON "NOT EMSCRIPTEN; NOT MSVC" OFF) +cmake_dependent_option(LIBCORO_FEATURE_SSL "Include SSL encryption features, Default=ON." ON "NOT EMSCRIPTEN; NOT MSVC" OFF) message("${PROJECT_NAME} LIBCORO_EXTERNAL_DEPENDENCIES = ${LIBCORO_EXTERNAL_DEPENDENCIES}") message("${PROJECT_NAME} LIBCORO_BUILD_TESTS = ${LIBCORO_BUILD_TESTS}") @@ -39,7 +40,6 @@ if(LIBCORO_EXTERNAL_DEPENDENCIES) if(LIBCORO_FEATURE_NETWORKING) find_package(c-ares CONFIG REQUIRED) endif() - find_package(tl-expected CONFIG REQUIRED) else() if(NOT LIBCORO_BUILD_TESTS) # Disable testing in expected @@ -51,7 +51,6 @@ else() set(CARES_INSTALL OFF CACHE INTERNAL "") add_subdirectory(vendor/c-ares/c-ares) endif() - add_subdirectory(vendor/tartanllama/expected) endif() set(LIBCORO_SOURCE_FILES @@ -112,18 +111,28 @@ if(LIBCORO_FEATURE_NETWORKING) endif() endif() +if(DEFINED EMSCRIPTEN) + add_compile_options(-fwasm-exceptions) + add_compile_options(-pthread) + add_compile_options(-matomics) + + add_link_options(-fwasm-exceptions) + add_link_options(-pthread) + add_link_options(-sPROXY_TO_PTHREAD) + add_link_options(-sALLOW_MEMORY_GROWTH) + add_link_options(-sEXIT_RUNTIME) +endif() + add_library(${PROJECT_NAME} STATIC ${LIBCORO_SOURCE_FILES}) set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX PREFIX "") target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) target_include_directories(${PROJECT_NAME} PUBLIC include) -if(MSVC) - # Not sure why this is only needed on Windows. - target_include_directories(${PROJECT_NAME} PUBLIC vendor/tartanllama/expected/include) -endif() + if(LIBCORO_FEATURE_PLATFORM) - target_link_libraries(${PROJECT_NAME} PUBLIC pthread tl::expected) + target_link_libraries(${PROJECT_NAME} PUBLIC pthread) target_compile_definitions(${PROJECT_NAME} PUBLIC LIBCORO_FEATURE_PLATFORM) -endif() + endif() + if(LIBCORO_FEATURE_NETWORKING) target_link_libraries(${PROJECT_NAME} PUBLIC c-ares::cares) target_compile_definitions(${PROJECT_NAME} PUBLIC LIBCORO_FEATURE_NETWORKING) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4d8509fd..44a42c3e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -64,7 +64,9 @@ elseif(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") target_compile_options(coro_task PUBLIC -Wall -Wextra -pipe) target_compile_options(coro_generator PUBLIC -Wall -Wextra -pipe) target_compile_options(coro_event PUBLIC -Wall -Wextra -pipe) - target_compile_options(coro_latch PUBLIC -Wall -Wextra -pipe) + if(LIBCORO_FEATURE_PLATFORM) + target_compile_options(coro_latch PUBLIC -Wall -Wextra -pipe) + endif() target_compile_options(coro_mutex PUBLIC -Wall -Wextra -pipe) target_compile_options(coro_thread_pool PUBLIC -Wall -Wextra -pipe) target_compile_options(coro_semaphore PUBLIC -Wall -Wextra -pipe) diff --git a/include/coro/detail/tl_expected.hpp b/include/coro/detail/tl_expected.hpp new file mode 100644 index 00000000..052a633b --- /dev/null +++ b/include/coro/detail/tl_expected.hpp @@ -0,0 +1,2655 @@ +/// +// expected - An implementation of std::expected with extensions +// Written in 2017 by Simon Brand (simonrbrand@gmail.com, @TartanLlama) +// +// Documentation available at http://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_EXPECTED_HPP +#define TL_EXPECTED_HPP + +#define TL_EXPECTED_VERSION_MAJOR 1 +#define TL_EXPECTED_VERSION_MINOR 0 +#define TL_EXPECTED_VERSION_PATCH 1 + +#include +#include +#include +#include + +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) + #define TL_EXPECTED_EXCEPTIONS_ENABLED +#endif + +#if (defined(_MSC_VER) && _MSC_VER == 1900) + #define TL_EXPECTED_MSVC2015 + #define TL_EXPECTED_MSVC2015_CONSTEXPR +#else + #define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && !defined(__clang__)) + #define TL_EXPECTED_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && !defined(__clang__)) + #define TL_EXPECTED_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && !defined(__clang__)) + #define TL_EXPECTED_GCC55 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && !defined(__clang__)) + // GCC < 5 doesn't support overloading on const&& for member functions + + #define TL_EXPECTED_NO_CONSTRR + // GCC < 5 doesn't support some standard C++11 type traits + #define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) std::has_trivial_copy_constructor + #define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign + + // This one will be different for GCC 5.7 if it's ever supported + #define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector +// for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) + #ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX + #define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl +{ +namespace detail +{ +template +struct is_trivially_copy_constructible : std::is_trivially_copy_constructible +{ +}; + #ifdef _GLIBCXX_VECTOR +template +struct is_trivially_copy_constructible> : std::false_type +{ +}; + #endif +} // namespace detail +} // namespace tl + #endif + + #define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) tl::detail::is_trivially_copy_constructible + #define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::is_trivially_copy_assignable + #define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible +#else + #define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) std::is_trivially_copy_constructible + #define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::is_trivially_copy_assignable + #define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible +#endif + +#if __cplusplus > 201103L + #define TL_EXPECTED_CXX14 +#endif + +#ifdef TL_EXPECTED_GCC49 + #define TL_EXPECTED_GCC49_CONSTEXPR +#else + #define TL_EXPECTED_GCC49_CONSTEXPR constexpr +#endif + +#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || defined(TL_EXPECTED_GCC49)) + #define TL_EXPECTED_11_CONSTEXPR +#else + #define TL_EXPECTED_11_CONSTEXPR constexpr +#endif + +namespace tl +{ +template +class expected; + +#ifndef TL_MONOSTATE_INPLACE_MUTEX + #define TL_MONOSTATE_INPLACE_MUTEX +class monostate +{ +}; + +struct in_place_t +{ + explicit in_place_t() = default; +}; +static constexpr in_place_t in_place{}; +#endif + +template +class unexpected +{ +public: + static_assert(!std::is_same::value, "E must not be void"); + + unexpected() = delete; + constexpr explicit unexpected(const E& e) : m_val(e) {} + + constexpr explicit unexpected(E&& e) : m_val(std::move(e)) {} + + constexpr const E& value() const& { return m_val; } + TL_EXPECTED_11_CONSTEXPR E& value() & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E&& value() && { return std::move(m_val); } + constexpr const E&& value() const&& { return std::move(m_val); } + +private: + E m_val; +}; + +template +constexpr bool operator==(const unexpected& lhs, const unexpected& rhs) +{ + return lhs.value() == rhs.value(); +} +template +constexpr bool operator!=(const unexpected& lhs, const unexpected& rhs) +{ + return lhs.value() != rhs.value(); +} +template +constexpr bool operator<(const unexpected& lhs, const unexpected& rhs) +{ + return lhs.value() < rhs.value(); +} +template +constexpr bool operator<=(const unexpected& lhs, const unexpected& rhs) +{ + return lhs.value() <= rhs.value(); +} +template +constexpr bool operator>(const unexpected& lhs, const unexpected& rhs) +{ + return lhs.value() > rhs.value(); +} +template +constexpr bool operator>=(const unexpected& lhs, const unexpected& rhs) +{ + return lhs.value() >= rhs.value(); +} + +template +unexpected::type> make_unexpected(E&& e) +{ + return unexpected::type>(std::forward(e)); +} + +struct unexpect_t +{ + unexpect_t() = default; +}; +static constexpr unexpect_t unexpect{}; + +namespace detail +{ +template +[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E&& e) +{ +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + throw std::forward(e); +#else + #ifdef _MSC_VER + __assume(0); + #else + __builtin_unreachable(); + #endif +#endif +} + +#ifndef TL_TRAITS_MUTEX + #define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template +using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template +using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template +struct conjunction : std::true_type +{ +}; +template +struct conjunction : B +{ +}; +template +struct conjunction : std::conditional, B>::type +{ +}; + + #if defined(_LIBCPP_VERSION) && __cplusplus == 201103L + #define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + #endif + + // In C++11 mode, there's an issue in libc++'s std::mem_fn + // which results in a hard-error when using it in a noexcept expression + // in some cases. This is a check to workaround the common failing case. + #ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +template +struct is_pointer_to_non_const_member_func : std::false_type +{ +}; +template +struct is_pointer_to_non_const_member_func : std::true_type +{ +}; +template +struct is_pointer_to_non_const_member_func : std::true_type +{ +}; +template +struct is_pointer_to_non_const_member_func : std::true_type +{ +}; +template +struct is_pointer_to_non_const_member_func : std::true_type +{ +}; +template +struct is_pointer_to_non_const_member_func : std::true_type +{ +}; +template +struct is_pointer_to_non_const_member_func : std::true_type +{ +}; + +template +struct is_const_or_const_ref : std::false_type +{ +}; +template +struct is_const_or_const_ref : std::true_type +{ +}; +template +struct is_const_or_const_ref : std::true_type +{ +}; + #endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template< + typename Fn, + typename... Args, + #ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + typename = enable_if_t::value && is_const_or_const_ref::value)>, + #endif + typename = enable_if_t>::value>, + int = 0> +constexpr auto invoke(Fn&& f, Args&&... args) noexcept(noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) +{ + return std::mem_fn(f)(std::forward(args)...); +} + +template>::value>> +constexpr auto invoke(Fn&& f, Args&&... args) noexcept(noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) +{ + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template +struct invoke_result_impl; + +template +struct invoke_result_impl(), std::declval()...), void()), Us...> +{ + using type = decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + + #if defined(_MSC_VER) && _MSC_VER <= 1900 +// TODO make a version which works with MSVC 2015 +template +struct is_swappable : std::true_type +{ +}; + +template +struct is_nothrow_swappable : std::true_type +{ +}; + #else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests +{ +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) +struct tag +{ +}; + +template +tag swap(T&, T&); +template +tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap +template +std::false_type can_swap(...) noexcept(false); +template(), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), std::declval()))); + +template +std::false_type uses_std(...); +template +std::is_same(), std::declval())), tag> uses_std(int); + +template +struct is_std_swap_noexcept + : std::integral_constant< + bool, + std::is_nothrow_move_constructible::value && std::is_nothrow_move_assignable::value> +{ +}; + +template +struct is_std_swap_noexcept : is_std_swap_noexcept +{ +}; + +template +struct is_adl_swap_noexcept : std::integral_constant(0))> +{ +}; +} // namespace swap_adl_tests + +template +struct is_swappable : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && std::is_move_constructible::value))> +{ +}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || is_swappable::value)> +{ +}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && ((decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> +{ +}; + #endif +#endif + +// Trait for checking if a type is a tl::expected +template +struct is_expected_impl : std::false_type +{ +}; +template +struct is_expected_impl> : std::true_type +{ +}; +template +using is_expected = is_expected_impl>; + +template +using expected_enable_forward_value = detail::enable_if_t< + std::is_constructible::value && !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using expected_enable_from_other = detail::enable_if_t< + std::is_constructible::value && std::is_constructible::value && + !std::is_constructible&>::value && !std::is_constructible&&>::value && + !std::is_constructible&>::value && + !std::is_constructible&&>::value && !std::is_convertible&, T>::value && + !std::is_convertible&&, T>::value && !std::is_convertible&, T>::value && + !std::is_convertible&&, T>::value>; + +template +using is_void_or = conditional_t::value, std::true_type, U>; + +template +using is_copy_constructible_or_void = is_void_or>; + +template +using is_move_constructible_or_void = is_void_or>; + +template +using is_copy_assignable_or_void = is_void_or>; + +template +using is_move_assignable_or_void = is_void_or>; + +} // namespace detail + +namespace detail +{ +struct no_init_t +{ +}; +static constexpr no_init_t no_init{}; + +// Implements the storage of the values, and ensures that the destructor is +// trivial if it can be. +// +// This specialization is for where neither `T` or `E` is trivially +// destructible, so the destructors must be called on destruction of the +// `expected` +template< + class T, + class E, + bool = std::is_trivially_destructible::value, + bool = std::is_trivially_destructible::value> +struct expected_storage_base +{ + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template::value>* = nullptr> + constexpr expected_storage_base(in_place_t, Args&&... args) : m_val(std::forward(args)...), + m_has_val(true) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, Args&&... args) + : m_val(il, std::forward(args)...), + m_has_val(true) + { + } + template::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + : m_unexpect(std::forward(args)...), + m_has_val(false) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args&&... args) + : m_unexpect(il, std::forward(args)...), + m_has_val(false) + { + } + + ~expected_storage_base() + { + if (m_has_val) + { + m_val.~T(); + } + else + { + m_unexpect.~unexpected(); + } + } + union + { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// This specialization is for when both `T` and `E` are trivially-destructible, +// so the destructor of the `expected` can be trivial. +template +struct expected_storage_base +{ + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template::value>* = nullptr> + constexpr expected_storage_base(in_place_t, Args&&... args) : m_val(std::forward(args)...), + m_has_val(true) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, Args&&... args) + : m_val(il, std::forward(args)...), + m_has_val(true) + { + } + template::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + : m_unexpect(std::forward(args)...), + m_has_val(false) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args&&... args) + : m_unexpect(il, std::forward(args)...), + m_has_val(false) + { + } + + ~expected_storage_base() = default; + union + { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// T is trivial, E is not. +template +struct expected_storage_base +{ + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template::value>* = nullptr> + constexpr expected_storage_base(in_place_t, Args&&... args) : m_val(std::forward(args)...), + m_has_val(true) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, Args&&... args) + : m_val(il, std::forward(args)...), + m_has_val(true) + { + } + template::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + : m_unexpect(std::forward(args)...), + m_has_val(false) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args&&... args) + : m_unexpect(il, std::forward(args)...), + m_has_val(false) + { + } + + ~expected_storage_base() + { + if (!m_has_val) + { + m_unexpect.~unexpected(); + } + } + + union + { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// E is trivial, T is not. +template +struct expected_storage_base +{ + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template::value>* = nullptr> + constexpr expected_storage_base(in_place_t, Args&&... args) : m_val(std::forward(args)...), + m_has_val(true) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, Args&&... args) + : m_val(il, std::forward(args)...), + m_has_val(true) + { + } + template::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + : m_unexpect(std::forward(args)...), + m_has_val(false) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args&&... args) + : m_unexpect(il, std::forward(args)...), + m_has_val(false) + { + } + + ~expected_storage_base() + { + if (m_has_val) + { + m_val.~T(); + } + } + union + { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is trivially-destructible +template +struct expected_storage_base +{ + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base() : m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_has_val(true) {} + + template::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + : m_unexpect(std::forward(args)...), + m_has_val(false) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args&&... args) + : m_unexpect(il, std::forward(args)...), + m_has_val(false) + { + } + + ~expected_storage_base() = default; + struct dummy + { + }; + union + { + unexpected m_unexpect; + dummy m_val; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is not trivially-destructible +template +struct expected_storage_base +{ + constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} + + template::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + : m_unexpect(std::forward(args)...), + m_has_val(false) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, Args&&... args) + : m_unexpect(il, std::forward(args)...), + m_has_val(false) + { + } + + ~expected_storage_base() + { + if (!m_has_val) + { + m_unexpect.~unexpected(); + } + } + + union + { + unexpected m_unexpect; + char m_dummy; + }; + bool m_has_val; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base +{ + using expected_storage_base::expected_storage_base; + + template + void construct(Args&&... args) noexcept + { + new (std::addressof(this->m_val)) T(std::forward(args)...); + this->m_has_val = true; + } + + template + void construct_with(Rhs&& rhs) noexcept + { + new (std::addressof(this->m_val)) T(std::forward(rhs).get()); + this->m_has_val = true; + } + + template + void construct_error(Args&&... args) noexcept + { + new (std::addressof(this->m_unexpect)) unexpected(std::forward(args)...); + this->m_has_val = false; + } + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + + // These assign overloads ensure that the most efficient assignment + // implementation is used while maintaining the strong exception guarantee. + // The problematic case is where rhs has a value, but *this does not. + // + // This overload handles the case where we can just copy-construct `T` + // directly into place without throwing. + template::value>* = nullptr> + void assign(const expected_operations_base& rhs) noexcept + { + if (!this->m_has_val && rhs.m_has_val) + { + geterr().~unexpected(); + construct(rhs.get()); + } + else + { + assign_common(rhs); + } + } + + // This overload handles the case where we can attempt to create a copy of + // `T`, then no-throw move it into place if the copy was successful. + template< + class U = T, + detail::enable_if_t< + !std::is_nothrow_copy_constructible::value && std::is_nothrow_move_constructible::value>* = nullptr> + void assign(const expected_operations_base& rhs) noexcept + { + if (!this->m_has_val && rhs.m_has_val) + { + T tmp = rhs.get(); + geterr().~unexpected(); + construct(std::move(tmp)); + } + else + { + assign_common(rhs); + } + } + + // This overload is the worst-case, where we have to move-construct the + // unexpected value into temporary storage, then try to copy the T into place. + // If the construction succeeds, then everything is fine, but if it throws, + // then we move the old unexpected value back into place before rethrowing the + // exception. + template< + class U = T, + detail::enable_if_t< + !std::is_nothrow_copy_constructible::value && !std::is_nothrow_move_constructible::value>* = nullptr> + void assign(const expected_operations_base& rhs) + { + if (!this->m_has_val && rhs.m_has_val) + { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try + { + construct(rhs.get()); + } + catch (...) + { + geterr() = std::move(tmp); + throw; + } + #else + construct(rhs.get()); + #endif + } + else + { + assign_common(rhs); + } + } + + // These overloads do the same as above, but for rvalues + template::value>* = nullptr> + void assign(expected_operations_base&& rhs) noexcept + { + if (!this->m_has_val && rhs.m_has_val) + { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } + else + { + assign_common(std::move(rhs)); + } + } + + template::value>* = nullptr> + void assign(expected_operations_base&& rhs) + { + if (!this->m_has_val && rhs.m_has_val) + { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try + { + construct(std::move(rhs).get()); + } + catch (...) + { + geterr() = std::move(tmp); + throw; + } + #else + construct(std::move(rhs).get()); + #endif + } + else + { + assign_common(std::move(rhs)); + } + } + +#else + + // If exceptions are disabled then we can just copy-construct + void assign(const expected_operations_base& rhs) noexcept + { + if (!this->m_has_val && rhs.m_has_val) + { + geterr().~unexpected(); + construct(rhs.get()); + } + else + { + assign_common(rhs); + } + } + + void assign(expected_operations_base&& rhs) noexcept + { + if (!this->m_has_val && rhs.m_has_val) + { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } + else + { + assign_common(rhs); + } + } + +#endif + + // The common part of move/copy assigning + template + void assign_common(Rhs&& rhs) + { + if (this->m_has_val) + { + if (rhs.m_has_val) + { + get() = std::forward(rhs).get(); + } + else + { + destroy_val(); + construct_error(std::forward(rhs).geterr()); + } + } + else + { + if (!rhs.m_has_val) + { + geterr() = std::forward(rhs).geterr(); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR T& get() & { return this->m_val; } + constexpr const T& get() const& { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR T&& get() && { return std::move(this->m_val); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const T&& get() const&& { return std::move(this->m_val); } +#endif + + TL_EXPECTED_11_CONSTEXPR unexpected& geterr() & { return this->m_unexpect; } + constexpr const unexpected& geterr() const& { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected&& geterr() && { return std::move(this->m_unexpect); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected&& geterr() const&& { return std::move(this->m_unexpect); } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base +{ + using expected_storage_base::expected_storage_base; + + template + void construct() noexcept + { + this->m_has_val = true; + } + + // This function doesn't use its argument, but needs it so that code in + // levels above this can work independently of whether T is void + template + void construct_with(Rhs&&) noexcept + { + this->m_has_val = true; + } + + template + void construct_error(Args&&... args) noexcept + { + new (std::addressof(this->m_unexpect)) unexpected(std::forward(args)...); + this->m_has_val = false; + } + + template + void assign(Rhs&& rhs) noexcept + { + if (!this->m_has_val) + { + if (rhs.m_has_val) + { + geterr().~unexpected(); + construct(); + } + else + { + geterr() = std::forward(rhs).geterr(); + } + } + else + { + if (!rhs.m_has_val) + { + construct_error(std::forward(rhs).geterr()); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR unexpected& geterr() & { return this->m_unexpect; } + constexpr const unexpected& geterr() const& { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected&& geterr() && { return std::move(this->m_unexpect); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected&& geterr() const&& { return std::move(this->m_unexpect); } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() + { + // no-op + } +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T and E are trivially copy constructible +template< + class T, + class E, + bool = is_void_or::value&& + TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> +struct expected_copy_base : expected_operations_base +{ + using expected_operations_base::expected_operations_base; +}; + +// This specialization is for when T or E are not trivially copy constructible +template +struct expected_copy_base : expected_operations_base +{ + using expected_operations_base::expected_operations_base; + + expected_copy_base() = default; + expected_copy_base(const expected_copy_base& rhs) : expected_operations_base(no_init) + { + if (rhs.has_value()) + { + this->construct_with(rhs); + } + else + { + this->construct_error(rhs.geterr()); + } + } + + expected_copy_base(expected_copy_base&& rhs) = default; + expected_copy_base& operator=(const expected_copy_base& rhs) = default; + expected_copy_base& operator=(expected_copy_base&& rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_EXPECTED_GCC49 +template< + class T, + class E, + bool = + is_void_or>::value&& std::is_trivially_move_constructible::value> +struct expected_move_base : expected_copy_base +{ + using expected_copy_base::expected_copy_base; +}; +#else +template +struct expected_move_base; +#endif +template +struct expected_move_base : expected_copy_base +{ + using expected_copy_base::expected_copy_base; + + expected_move_base() = default; + expected_move_base(const expected_move_base& rhs) = default; + + expected_move_base(expected_move_base&& rhs) noexcept(std::is_nothrow_move_constructible::value) + : expected_copy_base(no_init) + { + if (rhs.has_value()) + { + this->construct_with(std::move(rhs)); + } + else + { + this->construct_error(std::move(rhs.geterr())); + } + } + expected_move_base& operator=(const expected_move_base& rhs) = default; + expected_move_base& operator=(expected_move_base&& rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template< + class T, + class E, + bool = is_void_or< + T, + conjunction< + TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T), + TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T), + TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value&& TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value&& + TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value&& TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> +struct expected_copy_assign_base : expected_move_base +{ + using expected_move_base::expected_move_base; +}; + +template +struct expected_copy_assign_base : expected_move_base +{ + using expected_move_base::expected_move_base; + + expected_copy_assign_base() = default; + expected_copy_assign_base(const expected_copy_assign_base& rhs) = default; + + expected_copy_assign_base(expected_copy_assign_base&& rhs) = default; + expected_copy_assign_base& operator=(const expected_copy_assign_base& rhs) + { + this->assign(rhs); + return *this; + } + expected_copy_assign_base& operator=(expected_copy_assign_base&& rhs) = default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_EXPECTED_GCC49 +template< + class T, + class E, + bool = is_void_or< + T, + conjunction< + std::is_trivially_destructible, + std::is_trivially_move_constructible, + std::is_trivially_move_assignable>>::value&& std::is_trivially_destructible::value&& + std::is_trivially_move_constructible::value&& std::is_trivially_move_assignable::value> +struct expected_move_assign_base : expected_copy_assign_base +{ + using expected_copy_assign_base::expected_copy_assign_base; +}; +#else +template +struct expected_move_assign_base; +#endif + +template +struct expected_move_assign_base : expected_copy_assign_base +{ + using expected_copy_assign_base::expected_copy_assign_base; + + expected_move_assign_base() = default; + expected_move_assign_base(const expected_move_assign_base& rhs) = default; + + expected_move_assign_base(expected_move_assign_base&& rhs) = default; + + expected_move_assign_base& operator=(const expected_move_assign_base& rhs) = default; + + expected_move_assign_base& operator=(expected_move_assign_base&& rhs) noexcept( + std::is_nothrow_move_constructible::value&& std::is_nothrow_move_assignable::value) + { + this->assign(std::move(rhs)); + return *this; + } +}; + +// expected_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template< + class T, + class E, + bool EnableCopy = (is_copy_constructible_or_void::value && std::is_copy_constructible::value), + bool EnableMove = (is_move_constructible_or_void::value && std::is_move_constructible::value)> +struct expected_delete_ctor_base +{ + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base&) = default; + expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default; + expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default; + expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default; +}; + +template +struct expected_delete_ctor_base +{ + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base&) = default; + expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete; + expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default; + expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default; +}; + +template +struct expected_delete_ctor_base +{ + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; + expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default; + expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default; + expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default; +}; + +template +struct expected_delete_ctor_base +{ + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; + expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete; + expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default; + expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default; +}; + +// expected_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T and E are copy/move constructible + +// assignable +template< + class T, + class E, + bool EnableCopy = + (is_copy_constructible_or_void::value && std::is_copy_constructible::value && + is_copy_assignable_or_void::value && std::is_copy_assignable::value), + bool EnableMove = + (is_move_constructible_or_void::value && std::is_move_constructible::value && + is_move_assignable_or_void::value && std::is_move_assignable::value)> +struct expected_delete_assign_base +{ + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base&) = default; + expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; + expected_delete_assign_base& operator=(const expected_delete_assign_base&) = default; + expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = default; +}; + +template +struct expected_delete_assign_base +{ + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base&) = default; + expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; + expected_delete_assign_base& operator=(const expected_delete_assign_base&) = default; + expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = delete; +}; + +template +struct expected_delete_assign_base +{ + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base&) = default; + expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; + expected_delete_assign_base& operator=(const expected_delete_assign_base&) = delete; + expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = default; +}; + +template +struct expected_delete_assign_base +{ + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base&) = default; + expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; + expected_delete_assign_base& operator=(const expected_delete_assign_base&) = delete; + expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = delete; +}; + +// This is needed to be able to construct the expected_default_ctor_base which +// follows, while still conditionally deleting the default constructor. +struct default_constructor_tag +{ + explicit constexpr default_constructor_tag() = default; +}; + +// expected_default_ctor_base will ensure that expected has a deleted default +// consturctor if T is not default constructible. +// This specialization is for when T is default constructible +template::value || std::is_void::value> +struct expected_default_ctor_base +{ + constexpr expected_default_ctor_base() noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base const&) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = default; + expected_default_ctor_base& operator=(expected_default_ctor_base const&) noexcept = default; + expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; + +// This specialization is for when T is not default constructible +template +struct expected_default_ctor_base +{ + constexpr expected_default_ctor_base() noexcept = delete; + constexpr expected_default_ctor_base(expected_default_ctor_base const&) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = default; + expected_default_ctor_base& operator=(expected_default_ctor_base const&) noexcept = default; + expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; +} // namespace detail + +template +class bad_expected_access : public std::exception +{ +public: + explicit bad_expected_access(E e) : m_val(std::move(e)) {} + + virtual const char* what() const noexcept override { return "Bad expected access"; } + + const E& error() const& { return m_val; } + E& error() & { return m_val; } + const E&& error() const&& { return std::move(m_val); } + E&& error() && { return std::move(m_val); } + +private: + E m_val; +}; + +/// An `expected` object is an object that contains the storage for +/// another object and manages the lifetime of this contained object `T`. +/// Alternatively it could contain the storage for another unexpected object +/// `E`. The contained object may not be initialized after the expected object +/// has been initialized, and may not be destroyed before the expected object +/// has been destroyed. The initialization state of the contained object is +/// tracked by the expected object. +template +class expected : private detail::expected_move_assign_base, + private detail::expected_delete_ctor_base, + private detail::expected_delete_assign_base, + private detail::expected_default_ctor_base +{ + static_assert(!std::is_reference::value, "T must not be a reference"); + static_assert(!std::is_same::type>::value, "T must not be in_place_t"); + static_assert(!std::is_same::type>::value, "T must not be unexpect_t"); + static_assert(!std::is_same>::type>::value, "T must not be unexpected"); + static_assert(!std::is_reference::value, "E must not be a reference"); + + T* valptr() { return std::addressof(this->m_val); } + const T* valptr() const { return std::addressof(this->m_val); } + unexpected* errptr() { return std::addressof(this->m_unexpect); } + const unexpected* errptr() const { return std::addressof(this->m_unexpect); } + + template::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U& val() + { + return this->m_val; + } + TL_EXPECTED_11_CONSTEXPR unexpected& err() { return this->m_unexpect; } + + template::value>* = nullptr> + constexpr const U& val() const + { + return this->m_val; + } + constexpr const unexpected& err() const { return this->m_unexpect; } + + using impl_base = detail::expected_move_assign_base; + using ctor_base = detail::expected_default_ctor_base; + +public: + typedef T value_type; + typedef E error_type; + typedef unexpected unexpected_type; + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) && \ + !defined(TL_EXPECTED_GCC55) + template + TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) & + { + return and_then_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) && + { + return and_then_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto and_then(F&& f) const& + { + return and_then_impl(*this, std::forward(f)); + } + + #ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto and_then(F&& f) const&& + { + return and_then_impl(std::move(*this), std::forward(f)); + } + #endif + +#else + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F&& f) & -> decltype(and_then_impl(std::declval(), std::forward(f))) + { + return and_then_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F&& f) && -> decltype(and_then_impl(std::declval(), std::forward(f))) + { + return and_then_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto + and_then(F&& f) const& -> decltype(and_then_impl(std::declval(), std::forward(f))) + { + return and_then_impl(*this, std::forward(f)); + } + + #ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto + and_then(F&& f) const&& -> decltype(and_then_impl(std::declval(), std::forward(f))) + { + return and_then_impl(std::move(*this), std::forward(f)); + } + #endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) && \ + !defined(TL_EXPECTED_GCC55) + template + TL_EXPECTED_11_CONSTEXPR auto map(F&& f) & + { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto map(F&& f) && + { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto map(F&& f) const& + { + return expected_map_impl(*this, std::forward(f)); + } + template + constexpr auto map(F&& f) const&& + { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), std::declval())) map(F&& f) & + { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), std::declval())) map(F&& f) && + { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), std::declval())) map(F&& f) const& + { + return expected_map_impl(*this, std::forward(f)); + } + + #ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), std::declval())) map(F&& f) const&& + { + return expected_map_impl(std::move(*this), std::forward(f)); + } + #endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) && \ + !defined(TL_EXPECTED_GCC55) + template + TL_EXPECTED_11_CONSTEXPR auto transform(F&& f) & + { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto transform(F&& f) && + { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto transform(F&& f) const& + { + return expected_map_impl(*this, std::forward(f)); + } + template + constexpr auto transform(F&& f) const&& + { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), std::declval())) + transform(F&& f) & + { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), std::declval())) + transform(F&& f) && + { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), std::declval())) transform(F&& f) const& + { + return expected_map_impl(*this, std::forward(f)); + } + + #ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), std::declval())) + transform(F&& f) const&& + { + return expected_map_impl(std::move(*this), std::forward(f)); + } + #endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) && \ + !defined(TL_EXPECTED_GCC55) + template + TL_EXPECTED_11_CONSTEXPR auto map_error(F&& f) & + { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto map_error(F&& f) && + { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto map_error(F&& f) const& + { + return map_error_impl(*this, std::forward(f)); + } + template + constexpr auto map_error(F&& f) const&& + { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), std::declval())) map_error(F&& f) & + { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), std::declval())) + map_error(F&& f) && + { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(map_error_impl(std::declval(), std::declval())) map_error(F&& f) const& + { + return map_error_impl(*this, std::forward(f)); + } + + #ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(map_error_impl(std::declval(), std::declval())) map_error(F&& f) const&& + { + return map_error_impl(std::move(*this), std::forward(f)); + } + #endif +#endif + template + expected TL_EXPECTED_11_CONSTEXPR or_else(F&& f) & + { + return or_else_impl(*this, std::forward(f)); + } + + template + expected TL_EXPECTED_11_CONSTEXPR or_else(F&& f) && + { + return or_else_impl(std::move(*this), std::forward(f)); + } + + template + expected constexpr or_else(F&& f) const& + { + return or_else_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + expected constexpr or_else(F&& f) const&& + { + return or_else_impl(std::move(*this), std::forward(f)); + } +#endif + constexpr expected() = default; + constexpr expected(const expected& rhs) = default; + constexpr expected(expected&& rhs) = default; + expected& operator=(const expected& rhs) = default; + expected& operator=(expected&& rhs) = default; + + template::value>* = nullptr> + constexpr expected(in_place_t, Args&&... args) + : impl_base(in_place, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr expected(in_place_t, std::initializer_list il, Args&&... args) + : impl_base(in_place, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) + { + } + + template< + class G = E, + detail::enable_if_t::value>* = nullptr, + detail::enable_if_t::value>* = nullptr> + explicit constexpr expected(const unexpected& e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) + { + } + + template< + class G = E, + detail::enable_if_t::value>* = nullptr, + detail::enable_if_t::value>* = nullptr> + constexpr expected(unexpected const& e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) + { + } + + template< + class G = E, + detail::enable_if_t::value>* = nullptr, + detail::enable_if_t::value>* = nullptr> + explicit constexpr expected(unexpected&& e) noexcept(std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) + { + } + + template< + class G = E, + detail::enable_if_t::value>* = nullptr, + detail::enable_if_t::value>* = nullptr> + constexpr expected(unexpected&& e) noexcept(std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) + { + } + + template::value>* = nullptr> + constexpr explicit expected(unexpect_t, Args&&... args) + : impl_base(unexpect, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) + { + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args) + : impl_base(unexpect, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) + { + } + + template< + class U, + class G, + detail::enable_if_t::value && std::is_convertible::value)>* = + nullptr, + detail::expected_enable_from_other* = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(const expected& rhs) : ctor_base(detail::default_constructor_tag{}) + { + if (rhs.has_value()) + { + this->construct(*rhs); + } + else + { + this->construct_error(rhs.error()); + } + } + + template< + class U, + class G, + detail::enable_if_t<(std::is_convertible::value && std::is_convertible::value)>* = + nullptr, + detail::expected_enable_from_other* = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(const expected& rhs) : ctor_base(detail::default_constructor_tag{}) + { + if (rhs.has_value()) + { + this->construct(*rhs); + } + else + { + this->construct_error(rhs.error()); + } + } + + template< + class U, + class G, + detail::enable_if_t::value && std::is_convertible::value)>* = nullptr, + detail::expected_enable_from_other* = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(expected&& rhs) : ctor_base(detail::default_constructor_tag{}) + { + if (rhs.has_value()) + { + this->construct(std::move(*rhs)); + } + else + { + this->construct_error(std::move(rhs.error())); + } + } + + template< + class U, + class G, + detail::enable_if_t<(std::is_convertible::value && std::is_convertible::value)>* = nullptr, + detail::expected_enable_from_other* = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(expected&& rhs) : ctor_base(detail::default_constructor_tag{}) + { + if (rhs.has_value()) + { + this->construct(std::move(*rhs)); + } + else + { + this->construct_error(std::move(rhs.error())); + } + } + + template< + class U = T, + detail::enable_if_t::value>* = nullptr, + detail::expected_enable_forward_value* = nullptr> + explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U&& v) : expected(in_place, std::forward(v)) + { + } + + template< + class U = T, + detail::enable_if_t::value>* = nullptr, + detail::expected_enable_forward_value* = nullptr> + TL_EXPECTED_MSVC2015_CONSTEXPR expected(U&& v) : expected(in_place, std::forward(v)) + { + } + + template< + class U = T, + class G = T, + detail::enable_if_t::value>* = nullptr, + detail::enable_if_t::value>* = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, std::is_same>>::value && + std::is_constructible::value && std::is_assignable::value && + std::is_nothrow_move_constructible::value)>* = nullptr> + expected& operator=(U&& v) + { + if (has_value()) + { + val() = std::forward(v); + } + else + { + err().~unexpected(); + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } + + return *this; + } + + template< + class U = T, + class G = T, + detail::enable_if_t::value>* = nullptr, + detail::enable_if_t::value>* = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, std::is_same>>::value && + std::is_constructible::value && std::is_assignable::value && + std::is_nothrow_move_constructible::value)>* = nullptr> + expected& operator=(U&& v) + { + if (has_value()) + { + val() = std::forward(v); + } + else + { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try + { + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } + catch (...) + { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; +#endif + } + + return *this; + } + + template< + class G = E, + detail::enable_if_t::value && std::is_assignable::value>* = + nullptr> + expected& operator=(const unexpected& rhs) + { + if (!has_value()) + { + err() = rhs; + } + else + { + this->destroy_val(); + ::new (errptr()) unexpected(rhs); + this->m_has_val = false; + } + + return *this; + } + + template< + class G = E, + detail::enable_if_t::value && std::is_move_assignable::value>* = + nullptr> + expected& operator=(unexpected&& rhs) noexcept + { + if (!has_value()) + { + err() = std::move(rhs); + } + else + { + this->destroy_val(); + ::new (errptr()) unexpected(std::move(rhs)); + this->m_has_val = false; + } + + return *this; + } + + template::value>* = nullptr> + void emplace(Args&&... args) + { + if (has_value()) + { + val() = T(std::forward(args)...); + } + else + { + err().~unexpected(); + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } + } + + template::value>* = nullptr> + void emplace(Args&&... args) + { + if (has_value()) + { + val() = T(std::forward(args)...); + } + else + { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try + { + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } + catch (...) + { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; +#endif + } + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + void emplace(std::initializer_list il, Args&&... args) + { + if (has_value()) + { + T t(il, std::forward(args)...); + val() = std::move(t); + } + else + { + err().~unexpected(); + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } + } + + template< + class U, + class... Args, + detail::enable_if_t&, Args&&...>::value>* = nullptr> + void emplace(std::initializer_list il, Args&&... args) + { + if (has_value()) + { + T t(il, std::forward(args)...); + val() = std::move(t); + } + else + { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try + { + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } + catch (...) + { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; +#endif + } + } + +private: + using t_is_void = std::true_type; + using t_is_not_void = std::false_type; + using t_is_nothrow_move_constructible = std::true_type; + using move_constructing_t_can_throw = std::false_type; + using e_is_nothrow_move_constructible = std::true_type; + using move_constructing_e_can_throw = std::false_type; + + void swap_where_both_have_value(expected& /*rhs*/, t_is_void) noexcept + { + // swapping void is a no-op + } + + void swap_where_both_have_value(expected& rhs, t_is_not_void) + { + using std::swap; + swap(val(), rhs.val()); + } + + void swap_where_only_one_has_value(expected& rhs, t_is_void) noexcept(std::is_nothrow_move_constructible::value) + { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value(expected& rhs, t_is_not_void) + { + swap_where_only_one_has_value_and_t_is_not_void( + rhs, + typename std::is_nothrow_move_constructible::type{}, + typename std::is_nothrow_move_constructible::type{}); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected& rhs, t_is_nothrow_move_constructible, e_is_nothrow_move_constructible) noexcept + { + auto temp = std::move(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected& rhs, t_is_nothrow_move_constructible, move_constructing_e_can_throw) + { + auto temp = std::move(val()); + val().~T(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try + { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + catch (...) + { + val() = std::move(temp); + throw; + } +#else + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected& rhs, move_constructing_t_can_throw, t_is_nothrow_move_constructible) + { + auto temp = std::move(rhs.err()); + rhs.err().~unexpected_type(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try + { + ::new (rhs.valptr()) T(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + catch (...) + { + rhs.err() = std::move(temp); + throw; + } +#else + ::new (rhs.valptr()) T(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + +public: + template + detail::enable_if_t< + detail::is_swappable::value && detail::is_swappable::value && + (std::is_nothrow_move_constructible::value || std::is_nothrow_move_constructible::value)> + swap(expected& rhs) noexcept( + std::is_nothrow_move_constructible::value&& detail::is_nothrow_swappable::value&& + std::is_nothrow_move_constructible::value&& detail::is_nothrow_swappable::value) + { + if (has_value() && rhs.has_value()) + { + swap_where_both_have_value(rhs, typename std::is_void::type{}); + } + else if (!has_value() && rhs.has_value()) + { + rhs.swap(*this); + } + else if (has_value()) + { + swap_where_only_one_has_value(rhs, typename std::is_void::type{}); + } + else + { + using std::swap; + swap(err(), rhs.err()); + } + } + + constexpr const T* operator->() const { return valptr(); } + TL_EXPECTED_11_CONSTEXPR T* operator->() { return valptr(); } + + template::value>* = nullptr> + constexpr const U& operator*() const& + { + return val(); + } + template::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U& operator*() & + { + return val(); + } + template::value>* = nullptr> + constexpr const U&& operator*() const&& + { + return std::move(val()); + } + template::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U&& operator*() && + { + return std::move(val()); + } + + constexpr bool has_value() const noexcept { return this->m_has_val; } + constexpr explicit operator bool() const noexcept { return this->m_has_val; } + + template::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR const U& value() const& + { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U& value() & + { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR const U&& value() const&& + { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + template::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U&& value() && + { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + + constexpr const E& error() const& { return err().value(); } + TL_EXPECTED_11_CONSTEXPR E& error() & { return err().value(); } + constexpr const E&& error() const&& { return std::move(err().value()); } + TL_EXPECTED_11_CONSTEXPR E&& error() && { return std::move(err().value()); } + + template + constexpr T value_or(U&& v) const& + { + static_assert( + std::is_copy_constructible::value && std::is_convertible::value, + "T must be copy-constructible and convertible to from U&&"); + return bool(*this) ? **this : static_cast(std::forward(v)); + } + template + TL_EXPECTED_11_CONSTEXPR T value_or(U&& v) && + { + static_assert( + std::is_move_constructible::value && std::is_convertible::value, + "T must be move-constructible and convertible to from U&&"); + return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); + } +}; + +namespace detail +{ +template +using exp_t = typename detail::decay_t::value_type; +template +using err_t = typename detail::decay_t::error_type; +template +using ret_t = expected>; + +#ifdef TL_EXPECTED_CXX14 +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), *std::declval()))> +constexpr auto and_then_impl(Exp&& exp, F&& f) +{ + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval()))> +constexpr auto and_then_impl(Exp&& exp, F&& f) +{ + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) : Ret(unexpect, std::forward(exp).error()); +} +#else +template +struct TC; +template< + class Exp, + class F, + class Ret = decltype(detail::invoke(std::declval(), *std::declval())), + detail::enable_if_t>::value>* = nullptr> +auto and_then_impl(Exp&& exp, F&& f) -> Ret +{ + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template< + class Exp, + class F, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t>::value>* = nullptr> +constexpr auto and_then_impl(Exp&& exp, F&& f) -> Ret +{ + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) : Ret(unexpect, std::forward(exp).error()); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), *std::declval())), + detail::enable_if_t::value>* = nullptr> +constexpr auto expected_map_impl(Exp&& exp, F&& f) +{ + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f), *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), *std::declval())), + detail::enable_if_t::value>* = nullptr> +auto expected_map_impl(Exp&& exp, F&& f) +{ + using result = expected>; + if (exp.has_value()) + { + detail::invoke(std::forward(f), *std::forward(exp)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value>* = nullptr> +constexpr auto expected_map_impl(Exp&& exp, F&& f) +{ + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value>* = nullptr> +auto expected_map_impl(Exp&& exp, F&& f) +{ + using result = expected>; + if (exp.has_value()) + { + detail::invoke(std::forward(f)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} +#else +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), *std::declval())), + detail::enable_if_t::value>* = nullptr> + +constexpr auto expected_map_impl(Exp&& exp, F&& f) -> ret_t> +{ + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f), *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), *std::declval())), + detail::enable_if_t::value>* = nullptr> + +auto expected_map_impl(Exp&& exp, F&& f) -> expected> +{ + if (exp.has_value()) + { + detail::invoke(std::forward(f), *std::forward(exp)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value>* = nullptr> + +constexpr auto expected_map_impl(Exp&& exp, F&& f) -> ret_t> +{ + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value>* = nullptr> + +auto expected_map_impl(Exp&& exp, F&& f) -> expected> +{ + if (exp.has_value()) + { + detail::invoke(std::forward(f)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) && \ + !defined(TL_EXPECTED_GCC55) +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +constexpr auto map_error_impl(Exp&& exp, F&& f) +{ + using result = expected, detail::decay_t>; + return exp.has_value() ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); +} +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +auto map_error_impl(Exp&& exp, F&& f) +{ + using result = expected, monostate>; + if (exp.has_value()) + { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +constexpr auto map_error_impl(Exp&& exp, F&& f) +{ + using result = expected, detail::decay_t>; + return exp.has_value() ? result() + : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); +} +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +auto map_error_impl(Exp&& exp, F&& f) +{ + using result = expected, monostate>; + if (exp.has_value()) + { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#else +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +constexpr auto map_error_impl(Exp&& exp, F&& f) -> expected, detail::decay_t> +{ + using result = expected, detail::decay_t>; + + return exp.has_value() ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +auto map_error_impl(Exp&& exp, F&& f) -> expected, monostate> +{ + using result = expected, monostate>; + if (exp.has_value()) + { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +constexpr auto map_error_impl(Exp&& exp, F&& f) -> expected, detail::decay_t> +{ + using result = expected, detail::decay_t>; + + return exp.has_value() ? result() + : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); +} + +template< + class Exp, + class F, + detail::enable_if_t>::value>* = nullptr, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +auto map_error_impl(Exp&& exp, F&& f) -> expected, monostate> +{ + using result = expected, monostate>; + if (exp.has_value()) + { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template< + class Exp, + class F, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +constexpr auto or_else_impl(Exp&& exp, F&& f) +{ + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() ? std::forward(exp) + : detail::invoke(std::forward(f), std::forward(exp).error()); +} + +template< + class Exp, + class F, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +detail::decay_t or_else_impl(Exp&& exp, F&& f) +{ + return exp.has_value() + ? std::forward(exp) + : (detail::invoke(std::forward(f), std::forward(exp).error()), std::forward(exp)); +} +#else +template< + class Exp, + class F, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +auto or_else_impl(Exp&& exp, F&& f) -> Ret +{ + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() ? std::forward(exp) + : detail::invoke(std::forward(f), std::forward(exp).error()); +} + +template< + class Exp, + class F, + class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), + detail::enable_if_t::value>* = nullptr> +detail::decay_t or_else_impl(Exp&& exp, F&& f) +{ + return exp.has_value() + ? std::forward(exp) + : (detail::invoke(std::forward(f), std::forward(exp).error()), std::forward(exp)); +} +#endif +} // namespace detail + +template +constexpr bool operator==(const expected& lhs, const expected& rhs) +{ + return (lhs.has_value() != rhs.has_value()) ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); +} +template +constexpr bool operator!=(const expected& lhs, const expected& rhs) +{ + return (lhs.has_value() != rhs.has_value()) ? true : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); +} + +template +constexpr bool operator==(const expected& x, const U& v) +{ + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator==(const U& v, const expected& x) +{ + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator!=(const expected& x, const U& v) +{ + return x.has_value() ? *x != v : true; +} +template +constexpr bool operator!=(const U& v, const expected& x) +{ + return x.has_value() ? *x != v : true; +} + +template +constexpr bool operator==(const expected& x, const unexpected& e) +{ + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator==(const unexpected& e, const expected& x) +{ + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator!=(const expected& x, const unexpected& e) +{ + return x.has_value() ? true : x.error() != e.value(); +} +template +constexpr bool operator!=(const unexpected& e, const expected& x) +{ + return x.has_value() ? true : x.error() != e.value(); +} + +template< + class T, + class E, + detail::enable_if_t< + (std::is_void::value || std::is_move_constructible::value) && detail::is_swappable::value && + std::is_move_constructible::value && detail::is_swappable::value>* = nullptr> +void swap(expected& lhs, expected& rhs) noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} +} // namespace tl + +#endif diff --git a/include/coro/expected.hpp b/include/coro/expected.hpp index 4d218a34..ce4738bf 100644 --- a/include/coro/expected.hpp +++ b/include/coro/expected.hpp @@ -11,7 +11,7 @@ template using unexpected = std::unexpected; } // namespace coro #else - #include + #include "coro/detail/tl_expected.hpp" namespace coro { template diff --git a/include/coro/semaphore.hpp b/include/coro/semaphore.hpp index 6e26969c..cb53fd1f 100644 --- a/include/coro/semaphore.hpp +++ b/include/coro/semaphore.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/include/coro/thread_pool.hpp b/include/coro/thread_pool.hpp index 8757ee9c..56d81893 100644 --- a/include/coro/thread_pool.hpp +++ b/include/coro/thread_pool.hpp @@ -215,7 +215,6 @@ class thread_pool options m_opts; /// The background executor threads. std::vector m_threads; - /// Mutex for executor threads to sleep on the condition variable. std::mutex m_wait_mutex; /// Condition variable for each executor thread to wait on when no tasks are available. @@ -227,7 +226,6 @@ class thread_pool * @param idx The executor's idx for internal data structure accesses. */ auto executor(std::size_t idx) -> void; - /** * @param handle Schedules the given coroutine to be executed upon the first available thread. */ diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 0cfd6741..07d37f19 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -94,7 +94,6 @@ auto thread_pool::executor(std::size_t idx) -> void return m_size.load(std::memory_order::acquire) > 0 || m_shutdown_requested.load(std::memory_order::acquire); }); - // Process this batch until the queue is empty. while (!m_queue.empty()) { @@ -104,6 +103,7 @@ auto thread_pool::executor(std::size_t idx) -> void // Release the lock while executing the coroutine. lk.unlock(); handle.resume(); + m_size.fetch_sub(1, std::memory_order::release); lk.lock(); } diff --git a/test/test_task.cpp b/test/test_task.cpp index 35f5e14f..299e154f 100644 --- a/test/test_task.cpp +++ b/test/test_task.cpp @@ -430,14 +430,14 @@ TEST_CASE("task supports instantiation with non assignable type", "[task]") TEST_CASE("task promise sizeof", "[task]") { - REQUIRE(sizeof(coro::detail::promise) == sizeof(std::coroutine_handle<>) + sizeof(std::exception_ptr)); + REQUIRE(sizeof(coro::detail::promise) >= sizeof(std::coroutine_handle<>) + sizeof(std::exception_ptr)); REQUIRE( sizeof(coro::detail::promise) == sizeof(std::coroutine_handle<>) + sizeof(std::variant)); REQUIRE( - sizeof(coro::detail::promise) == + sizeof(coro::detail::promise) >= sizeof(std::coroutine_handle<>) + sizeof(std::variant)); REQUIRE( - sizeof(coro::detail::promise>) == + sizeof(coro::detail::promise>) >= sizeof(std::coroutine_handle<>) + sizeof(std::variant, std::exception_ptr>)); } diff --git a/vendor/tartanllama/expected b/vendor/tartanllama/expected deleted file mode 160000 index d11e06b5..00000000 --- a/vendor/tartanllama/expected +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d11e06b5bb39d59abb87ed67cf73d9a65a3bb3d9