diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e808add..d3d73b75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,9 @@ on: - 'tests/**' - 'CMakeLists.txt' - 'cmake/**' + - 'nix/**' + - 'flake.nix' + - 'flake.lock' pull_request: branches: - main @@ -19,66 +22,190 @@ on: - 'tests/**' - 'CMakeLists.txt' - 'cmake/**' + - 'nix/**' + - 'flake.nix' + - 'flake.lock' jobs: - build-linux: + linux: runs-on: group: runners-arm64 strategy: fail-fast: false matrix: + mode: + - cxx20 + - cxx23 configuration: - - Debug - - Release + - Debug + - Release compiler: - - gcc:13 - - gcc:14 - - gcc:15 - - clang:18 - - clang:19 - - clang:20 + - "gcc:12" + - "clang:16" + - "clang:17" + - "gcc:13" + - "gcc:14" + - "gcc:15" + - "clang:18" + - "clang:19" + - "clang:20" + exclude: + - mode: cxx23 + compiler: "gcc:12" + - mode: cxx23 + compiler: "clang:16" + - mode: cxx23 + compiler: "clang:17" container: libfn/ci-build-${{ matrix.compiler }} steps: - uses: actions/checkout@v4 - - name: Verify compiler compatibility - env: - SOURCE: | - #include - #include - #include - int main() { - using type1=std::expected; - using type2=std::optional; - return type1{1}.and_then([](int i) -> type1 { std::puts("OK expected"); return {i-1}; }).value() - + type2{2}.and_then([](int i) -> type2 { std::puts("OK optional"); return {i-2}; }).value(); - } + - name: Prepare build run: | - printf "CXX=%s\nCXXFLAGS=%s\n" "$CXX" "$CXXFLAGS" - $CXX --version | head -1 - FILE=$(mktemp --tmpdir XXXXXX.cpp) - printf "$SOURCE\n" > $FILE - OUT=$(mktemp --tmpdir XXXXXX) - $CXX -std=c++2b $CXXFLAGS -Wall $FILE -o $OUT - $OUT + mkdir .build + cd .build + cmake -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} .. + COMPILER=$( grep -iE "^CMAKE_CXX_COMPILER:FILEPATH=" CMakeCache.txt | sed -n 's/^[^=]*=//p' ) + FLAGS=$( grep -iE "^CMAKE_CXX_FLAGS(_${{ matrix.configuration }})?:STRING" CMakeCache.txt | sed -n 's/^[^=]*=//p' | tr '\n' ' ' ) + printf "C++ compiler path: %s\n" "$COMPILER" + "$COMPILER" --version + printf "C++ compilation options: %s\n" "$FLAGS" + + - name: Build and test ${{ matrix.mode }} + run: | + cd .build + cmake --build . --target ${{ matrix.mode }} + ctest -L ${{ matrix.mode }} --output-on-failure + + # Build and test all for one arbitrary configuration + - name: Build and test all + if: ${{ matrix.compiler == 'gcc:14' && matrix.mode == 'cxx23' }} + run: | + cd .build + cmake --build . --target clean + cmake --build . + ctest --output-on-failure + + macos: + runs-on: macos-${{ matrix.osver }} + strategy: + fail-fast: false + matrix: + mode: + - cxx20 + - cxx23 + configuration: + - Debug + - Release + compiler: + - appleclang + - clang + osver: + - 14 + - 15 + clangrelease: + - NA + - 18 + exclude: + - compiler: clang + osver: 14 + - compiler: clang + clangrelease: NA + - compiler: appleclang + clangrelease: 18 + - mode: cxx23 + compiler: appleclang + osver: 14 + steps: + - uses: actions/checkout@v4 - name: Prepare build run: | mkdir .build cd .build + if [[ "${{ matrix.compiler }}" == "appleclang" ]]; then + export CXX="$(which clang++)" + export CC="$(which clang)" + fi + if [[ "${{ matrix.compiler }}" == "clang" ]]; then + export CXX="$(brew --prefix llvm@${{ matrix.clangrelease }})/bin/clang++" + export CC="$(brew --prefix llvm@${{ matrix.clangrelease }})/bin/clang" + fi cmake -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} .. - COMPILER=$( grep -E "^CMAKE_CXX_COMPILER:FILEPATH=" CMakeCache.txt | sed -e "s|^[^=]*=||" ) - FLAGS=$( grep -iE "^CMAKE_CXX_FLAGS:STRING=" CMakeCache.txt | sed -e "s|^[^=]*=||" ) - printf "C++ compiler: %s\n" "$COMPILER" - printf "C++ compiler version: %s\n" "$( $COMPILER --version | head -1 )" + COMPILER=$( grep -iE "^CMAKE_CXX_COMPILER:FILEPATH=" CMakeCache.txt | sed -n 's/^[^=]*=//p' ) + FLAGS=$( grep -iE "^CMAKE_CXX_FLAGS(_${{ matrix.configuration }})?:STRING" CMakeCache.txt | sed -n 's/^[^=]*=//p' | tr '\n' ' ' ) + printf "C++ compiler path: %s\n" "$COMPILER" + "$COMPILER" --version printf "C++ compilation options: %s\n" "$FLAGS" - - name: Build all + - name: Build and test ${{ matrix.mode }} run: | cd .build - cmake --build . --target all + cmake --build . --target ${{ matrix.mode }} + ctest -L ${{ matrix.mode }} --output-on-failure - - name: Run tests + # Build and test all for one arbitrary configuration + - name: Build and test all + if: ${{ matrix.compiler == 'appleclang' && matrix.osver == '15' && matrix.mode == 'cxx23' }} run: | cd .build + cmake --build . --target clean + cmake --build . ctest --output-on-failure + + windows: + runs-on: windows-${{ matrix.osver }} + strategy: + fail-fast: false + matrix: + mode: + - cxx20 + configuration: + - Debug + - Release + compiler: + - "Visual Studio 17 2022" + osver: + - 2025 + steps: + - uses: actions/checkout@v4 + + - name: Prepare build + shell: bash + run: | + mkdir .build + cd .build + cmake -G "${{ matrix.compiler }}" -A x64 .. + LINKER=$( grep -iE "^CMAKE_LINKER:FILEPATH=" CMakeCache.txt | sed -n 's/^[^=]*=//p' ) + FLAGS=$( grep -iE "^CMAKE_CXX_FLAGS(_${{ matrix.configuration }})?:STRING" CMakeCache.txt | sed -n 's/^[^=]*=//p' | tr '\n' ' ' ) + printf "C++ linker path: %s\n" "$LINKER" + "$LINKER" . || true + printf "C++ compilation options: %s\n" "$FLAGS" + + - name: Build and test ${{ matrix.mode }} + shell: bash + run: | + cd .build + cmake --build . --target ${{ matrix.mode }} --config ${{ matrix.configuration }} + ctest -L ${{ matrix.mode }} -C ${{ matrix.configuration }} --output-on-failure + + nixos: + runs-on: + group: runners-intel + strategy: + fail-fast: false + matrix: + compiler: + - gcc + - clang + steps: + - uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Build and test + run: nix -L build '.#${{matrix.compiler}}' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 76644096..1567ea64 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -65,20 +65,19 @@ jobs: mkdir .build cd .build cmake -DCMAKE_C_FLAGS="$COVERAGE_OPTS" -DCMAKE_CXX_FLAGS="$COVERAGE_OPTS" .. - COMPILER=$( grep -E "^CMAKE_CXX_COMPILER:FILEPATH=" CMakeCache.txt | sed -e "s|^[^=]*=||" ) - FLAGS=$( grep -iE "^CMAKE_CXX_FLAGS:STRING=" CMakeCache.txt | sed -e "s|^[^=]*=||" ) - printf "C++ compiler: %s\n" "$COMPILER" - printf "C++ compiler version: %s\n" "$( $COMPILER --version | head -1 )" + COMPILER=$( grep -iE "^CMAKE_CXX_COMPILER:FILEPATH=" CMakeCache.txt | sed -n 's/^[^=]*=//p' ) + FLAGS=$( grep -iE "^CMAKE_CXX_FLAGS(_${{ matrix.configuration }})?:STRING" CMakeCache.txt | sed -n 's/^[^=]*=//p' | tr '\n' ' ' ) + printf "C++ compiler path: %s\n" "$COMPILER" + $COMPILER --version printf "C++ compilation options: %s\n" "$FLAGS" printf "gcov version: %s\n" "$( $GCOV --version | head -1 )" - [[ "$( realpath $CXX )" == "$( realpath $COMPILER )" ]] || exit 13 - name: Run coverage target shell: bash run: | cd .build cmake --build . - ctest -L tests_fn -j1 # generate .gcda files + ctest -L 'tests_p?fn' -j1 # generate .gcda files $GCOV -pbc -r -s $( realpath .. ) $( find tests -type f -name '*.gcno' ) # generate .gcov files - name: Upload .gcov files diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml deleted file mode 100644 index a2bd59af..00000000 --- a/.github/workflows/nix.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: nix - -on: - push: - branches: - - main - paths: - - '.github/workflows/nix.yml' - - 'flake.nix' - - 'flake.lock' - - 'nix/**' - - 'include/**' - - 'tests/**' - - 'CMakeLists.txt' - - 'cmake/**' - - 'README.md' - - 'LICENSE.md' - pull_request: - branches: - - main - paths: - - '.github/workflows/nix.yml' - - 'flake.nix' - - 'flake.lock' - - 'nix/**' - - 'include/**' - - 'tests/**' - - 'CMakeLists.txt' - - 'cmake/**' - - 'README.md' - - 'LICENSE.md' - -jobs: - build: - runs-on: - group: runners-intel - strategy: - fail-fast: false - matrix: - compiler: - - gcc - - clang - steps: - - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v27 - with: - extra_nix_config: | - access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - - - name: Build and test - run: nix -L build '.#${{matrix.compiler}}' diff --git a/CMakeLists.txt b/CMakeLists.txt index bf367450..5f4fe4b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,17 +33,11 @@ endif() include(Ccache) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options( - -stdlib=libc++ - ) - add_link_options( - -lc++ - ) -endif() +# Phony targets for all supported C++ versions +add_custom_target(cxx20) +add_custom_target(cxx23) add_subdirectory(include) diff --git a/cmake/CompilationOptions.cmake b/cmake/CompilationOptions.cmake new file mode 100644 index 00000000..75e7d2df --- /dev/null +++ b/cmake/CompilationOptions.cmake @@ -0,0 +1,60 @@ +# Add compilation options appropriate for the current compiler + +# Note, we do not select libc++ like an example below. Instead the user should +# use the CXXFLAGS environment variable for this option. +# +# if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND (NOT APPLE)) +# add_compile_options(-stdlib=libc++) +# add_link_options(-lc++) +# endif() + + +function(append_compilation_options) + set(options WARNINGS OPTIMIZATION INTERFACE) + set(Options_NAME ${ARGV0}) + cmake_parse_arguments(Options "${options}" "" "" ${ARGN}) + + if(NOT DEFINED Options_NAME) + message(FATAL_ERROR "NAME must be set") + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + if(Options_WARNINGS) + # disable C4456: declaration of 'b' hides previous local declaration + # disable C4244: 'initializing': conversion from '_Ty' to '_Ty', possible loss of data + # disable C4101: 'e': unreferenced local variable + target_compile_options(${Options_NAME} PRIVATE /W4 /wd4456 /wd4244 /wd4101 ) + endif() + + if(Options_OPTIMIZATION) + target_compile_options(${Options_NAME} PRIVATE $,/Od,/Ox>) + endif() + + if(Options_INTERFACE) + target_compile_options(${Options_NAME} INTERFACE /Za /permissive-) + # This will disable `unexpected` in global namespace from eh.h + target_compile_definitions(${Options_NAME} INTERFACE _HAS_CXX23) + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang") + if(Options_WARNINGS) + target_compile_options(${Options_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wno-redundant-move) + endif() + + if(Options_OPTIMIZATION) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_options(${Options_NAME} PRIVATE -O0 -fsanitize=address -static-libasan -fno-omit-frame-pointer) + target_link_options(${Options_NAME} PRIVATE -fsanitize=address) + else() + target_compile_options(${Options_NAME} PRIVATE $,-O0 -fno-omit-frame-pointer,-O2>) + endif() + endif() + + if(Options_INTERFACE) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_options(${Options_NAME} INTERFACE -Wno-non-template-friend) + endif() + + target_compile_options(${Options_NAME} INTERFACE -Wno-missing-braces) + endif() + endif() +endfunction() diff --git a/cmake/TargetGenerator.cmake b/cmake/TargetGenerator.cmake index 258ae29f..0bfea092 100644 --- a/cmake/TargetGenerator.cmake +++ b/cmake/TargetGenerator.cmake @@ -1,23 +1,14 @@ # Generate target for a given source file, and optionally add it to tests -function(setup_target_for_file) - set(options ADD_TEST) +function(create_target_for_file) set(oneValueArgs NAME SOURCE SOURCE_ROOT NEW_SOURCE) - set(multiValueArgs DEPENDENCIES COMPILE_OPTIONS TEST_OPTIONS TEST_LABELS) - cmake_parse_arguments(Generator "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(multiValueArgs DEPENDENCIES) + cmake_parse_arguments(Generator "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT DEFINED Generator_SOURCE) message(FATAL_ERROR "SOURCE must be set") endif() - if(DEFINED Generator_TEST_OPTIONS AND (NOT Generator_ADD_TEST)) - message(FATAL_ERROR "ADD_TEST must be set if TEST_OPTIONS is set") - endif() - - if(DEFINED Generator_TEST_LABELS AND (NOT Generator_ADD_TEST)) - message(FATAL_ERROR "ADD_TEST must be set if TEST_LABELS is set") - endif() - if(NOT DEFINED Generator_NAME) string(REGEX REPLACE "[\\./]" "-" Generator_NAME ${Generator_SOURCE}) endif() @@ -36,18 +27,4 @@ function(setup_target_for_file) endif() target_link_libraries(${Generator_NAME} ${Generator_DEPENDENCIES}) - - target_compile_options(${Generator_NAME} PRIVATE ${Generator_COMPILE_OPTIONS}) - - if(Generator_ADD_TEST) - add_test( - NAME ${Generator_NAME} - COMMAND ${Generator_NAME} ${Generator_TEST_OPTIONS} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - - if(Generator_TEST_LABELS) - set_property(TEST ${Generator_NAME} PROPERTY LABELS "${Generator_TEST_LABELS}") - endif() - endif() endfunction() diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 74d55259..1a0571d6 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -2,9 +2,57 @@ cmake_minimum_required(VERSION 3.25) project(include) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) +include(CompilationOptions) + +### include/pfn + +# Pls keep the filenames sorted +set(INCLUDE_PFN_HEADERS + pfn/expected.hpp +) + +add_library(include_pfn INTERFACE) +set_property(TARGET include_pfn PROPERTY CXX_STANDARD 20) +target_sources(include_pfn INTERFACE + FILE_SET include_pfn_headers + TYPE HEADERS + FILES ${INCLUDE_PFN_HEADERS}) +append_compilation_options(include_pfn INTERFACE) +target_include_directories(include_pfn SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +install(TARGETS include_pfn + FILE_SET include_pfn_headers + DESTINATION include) + +include(TargetGenerator) + +# Generate sentinel target for each individual header, as a basic sanity check +foreach(mode 20 23) + foreach(source IN ITEMS ${INCLUDE_PFN_HEADERS}) + string(REGEX REPLACE "^(pfn)/(detail|)/?([^\.]+)\.hpp$" "\\1_\\2\\3" root_name ${source}) + set(target "sentinel_${root_name}_cxx${mode}") + + create_target_for_file( + NAME "${target}" + SOURCE "${source}" + NEW_SOURCE "#include <${source}>\nint main() {}\n" + SOURCE_ROOT "${CMAKE_BINARY_DIR}/sentinel" + DEPENDENCIES include_pfn + ) + append_compilation_options("${target}" WARNINGS) + add_dependencies("cxx${mode}" "${target}") + set_property(TARGET "${target}" PROPERTY CXX_STANDARD "${mode}") + target_compile_definitions("${target}" PRIVATE LIBFN_MODE=${mode}) + + unset(target) + unset(root_name) + endforeach() +endforeach() + +### include/fn + # Pls keep the filenames sorted set(INCLUDE_FN_HEADERS fn/detail/functional.hpp @@ -38,28 +86,40 @@ set(INCLUDE_FN_HEADERS ) add_library(include_fn INTERFACE) +set_property(TARGET include_fn PROPERTY CXX_STANDARD 23) target_sources(include_fn INTERFACE FILE_SET include_fn_headers TYPE HEADERS FILES ${INCLUDE_FN_HEADERS}) -target_include_directories(include_fn INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +append_compilation_options(include_fn INTERFACE) +target_include_directories(include_fn SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +# TODO uncomment when include_pfn is ready +# target_link_libraries(include_fn INTERFACE include_pfn) install(TARGETS include_fn FILE_SET include_fn_headers DESTINATION include) -include(TargetGenerator) - # Generate sentinel target for each individual header, as a basic sanity check -foreach(source IN ITEMS ${INCLUDE_FN_HEADERS}) - string(REGEX REPLACE "^fn/(detail|)/?([^\.]+)\.hpp$" "\\1\\2" root_name ${source}) - setup_target_for_file( - NAME "include_fn_sentinel_${root_name}" - SOURCE "${source}" - NEW_SOURCE "#include <${source}>\nint main() {}\n" - DEPENDENCIES include_fn - COMPILE_OPTIONS -Wall -Wextra -Wpedantic -Wno-missing-braces - ) - unset(root_name) +# TODO add 20 when we are compatible with C++20 +foreach(mode 23) + foreach(source IN ITEMS ${INCLUDE_FN_HEADERS}) + string(REGEX REPLACE "^(fn)/(detail|)/?([^\.]+)\.hpp$" "\\1_\\2\\3" root_name ${source}) + set(target "sentinel_${root_name}_cxx${mode}") + + create_target_for_file( + NAME "${target}" + SOURCE "${source}" + NEW_SOURCE "#include <${source}>\nint main() {}\n" + SOURCE_ROOT "${CMAKE_BINARY_DIR}/sentinel" + DEPENDENCIES include_fn + ) + append_compilation_options("${target}" WARNINGS) + add_dependencies("cxx${mode}" "${target}") + set_property(TARGET "${target}" PROPERTY CXX_STANDARD "${mode}") + target_compile_definitions("${target}" PRIVATE LIBFN_MODE=${mode}) + unset(target) + unset(root_name) + endforeach() endforeach() diff --git a/include/fn/and_then.hpp b/include/fn/and_then.hpp index d0388455..55348714 100644 --- a/include/fn/and_then.hpp +++ b/include/fn/and_then.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_AND_THEN -#define INCLUDE_FUNCTIONAL_AND_THEN +#ifndef INCLUDE_FN_AND_THEN +#define INCLUDE_FN_AND_THEN #include #include diff --git a/include/fn/choice.hpp b/include/fn/choice.hpp index 0e6a7f20..cf88855e 100644 --- a/include/fn/choice.hpp +++ b/include/fn/choice.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_CHOICE -#define INCLUDE_FUNCTIONAL_CHOICE +#ifndef INCLUDE_FN_CHOICE +#define INCLUDE_FN_CHOICE #include #include diff --git a/include/fn/concepts.hpp b/include/fn/concepts.hpp index ae7f2f86..6ca38035 100644 --- a/include/fn/concepts.hpp +++ b/include/fn/concepts.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_CONCEPTS -#define INCLUDE_FUNCTIONAL_CONCEPTS +#ifndef INCLUDE_FN_CONCEPTS +#define INCLUDE_FN_CONCEPTS #include #include diff --git a/include/fn/detail/functional.hpp b/include/fn/detail/functional.hpp index ccec3d61..2ee2ce0f 100644 --- a/include/fn/detail/functional.hpp +++ b/include/fn/detail/functional.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_DETAIL_FUNCTIONAL -#define INCLUDE_FUNCTIONAL_DETAIL_FUNCTIONAL +#ifndef INCLUDE_FN_DETAIL_FUNCTIONAL +#define INCLUDE_FN_DETAIL_FUNCTIONAL #include #include diff --git a/include/fn/detail/fwd.hpp b/include/fn/detail/fwd.hpp index 10cacccc..8b00b4e9 100644 --- a/include/fn/detail/fwd.hpp +++ b/include/fn/detail/fwd.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_DETAIL_FWD -#define INCLUDE_FUNCTIONAL_DETAIL_FWD +#ifndef INCLUDE_FN_DETAIL_FWD +#define INCLUDE_FN_DETAIL_FWD namespace fn { // NOTE Some forward declarations can lead to hard to troubleshoot compilation diff --git a/include/fn/detail/fwd_macro.hpp b/include/fn/detail/fwd_macro.hpp index 8383dacd..096dd8ec 100644 --- a/include/fn/detail/fwd_macro.hpp +++ b/include/fn/detail/fwd_macro.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_DETAIL_FWD_MACRO -#define INCLUDE_FUNCTIONAL_DETAIL_FWD_MACRO +#ifndef INCLUDE_FN_DETAIL_FWD_MACRO +#define INCLUDE_FN_DETAIL_FWD_MACRO // This FWD macro is a functional equivalent to std::forward(v), // but it saves compilation time (and typing) when used frequently. diff --git a/include/fn/detail/meta.hpp b/include/fn/detail/meta.hpp index 6df6fddc..50a5eead 100644 --- a/include/fn/detail/meta.hpp +++ b/include/fn/detail/meta.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_DETAIL_META -#define INCLUDE_FUNCTIONAL_DETAIL_META +#ifndef INCLUDE_FN_DETAIL_META +#define INCLUDE_FN_DETAIL_META #include #include diff --git a/include/fn/detail/pack_impl.hpp b/include/fn/detail/pack_impl.hpp index e680302c..26d15c47 100644 --- a/include/fn/detail/pack_impl.hpp +++ b/include/fn/detail/pack_impl.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_DETAIL_PACK_IMPL -#define INCLUDE_FUNCTIONAL_DETAIL_PACK_IMPL +#ifndef INCLUDE_FN_DETAIL_PACK_IMPL +#define INCLUDE_FN_DETAIL_PACK_IMPL #include #include diff --git a/include/fn/detail/traits.hpp b/include/fn/detail/traits.hpp index 80d229bd..629c4560 100644 --- a/include/fn/detail/traits.hpp +++ b/include/fn/detail/traits.hpp @@ -1,5 +1,10 @@ -#ifndef INCLUDE_FUNCTIONAL_DETAIL_TRAITS -#define INCLUDE_FUNCTIONAL_DETAIL_TRAITS +// Copyright (c) 2024 Bronek Kozicki +// +// Distributed under the ISC License. See accompanying file LICENSE.md +// or copy at https://opensource.org/licenses/ISC + +#ifndef INCLUDE_FN_DETAIL_TRAITS +#define INCLUDE_FN_DETAIL_TRAITS #include diff --git a/include/fn/detail/variadic_union.hpp b/include/fn/detail/variadic_union.hpp index 2d71c1af..a9106447 100644 --- a/include/fn/detail/variadic_union.hpp +++ b/include/fn/detail/variadic_union.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_DETAIL_VARIADIC_UNION -#define INCLUDE_FUNCTIONAL_DETAIL_VARIADIC_UNION +#ifndef INCLUDE_FN_DETAIL_VARIADIC_UNION +#define INCLUDE_FN_DETAIL_VARIADIC_UNION #include #include diff --git a/include/fn/expected.hpp b/include/fn/expected.hpp index ca4b615e..005e19c9 100644 --- a/include/fn/expected.hpp +++ b/include/fn/expected.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_EXPECTED -#define INCLUDE_FUNCTIONAL_EXPECTED +#ifndef INCLUDE_FN_EXPECTED +#define INCLUDE_FN_EXPECTED #include #include diff --git a/include/fn/fail.hpp b/include/fn/fail.hpp index f75670b6..7528070e 100644 --- a/include/fn/fail.hpp +++ b/include/fn/fail.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_FAIL -#define INCLUDE_FUNCTIONAL_FAIL +#ifndef INCLUDE_FN_FAIL +#define INCLUDE_FN_FAIL #include #include diff --git a/include/fn/filter.hpp b/include/fn/filter.hpp index 4abeeb2a..3f60e4f2 100644 --- a/include/fn/filter.hpp +++ b/include/fn/filter.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_FILTER -#define INCLUDE_FUNCTIONAL_FILTER +#ifndef INCLUDE_FN_FILTER +#define INCLUDE_FN_FILTER #include #include diff --git a/include/fn/functional.hpp b/include/fn/functional.hpp index 34e20e1b..7078fced 100644 --- a/include/fn/functional.hpp +++ b/include/fn/functional.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_FUNCTIONAL -#define INCLUDE_FUNCTIONAL_FUNCTIONAL +#ifndef INCLUDE_FN_FUNCTIONAL +#define INCLUDE_FN_FUNCTIONAL #include diff --git a/include/fn/functor.hpp b/include/fn/functor.hpp index b6f8abee..982a74aa 100644 --- a/include/fn/functor.hpp +++ b/include/fn/functor.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_FUNCTOR -#define INCLUDE_FUNCTIONAL_FUNCTOR +#ifndef INCLUDE_FN_FUNCTOR +#define INCLUDE_FN_FUNCTOR #include #include diff --git a/include/fn/fwd.hpp b/include/fn/fwd.hpp index 6b8e530c..82ce460e 100644 --- a/include/fn/fwd.hpp +++ b/include/fn/fwd.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_FWD -#define INCLUDE_FUNCTIONAL_FWD +#ifndef INCLUDE_FN_FWD +#define INCLUDE_FN_FWD // For exposition only #include diff --git a/include/fn/inspect.hpp b/include/fn/inspect.hpp index 93af50cd..b2af9d20 100644 --- a/include/fn/inspect.hpp +++ b/include/fn/inspect.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_INSPECT -#define INCLUDE_FUNCTIONAL_INSPECT +#ifndef INCLUDE_FN_INSPECT +#define INCLUDE_FN_INSPECT #include #include diff --git a/include/fn/inspect_error.hpp b/include/fn/inspect_error.hpp index cdff4594..a9c65ec6 100644 --- a/include/fn/inspect_error.hpp +++ b/include/fn/inspect_error.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_INSPECT_ERROR -#define INCLUDE_FUNCTIONAL_INSPECT_ERROR +#ifndef INCLUDE_FN_INSPECT_ERROR +#define INCLUDE_FN_INSPECT_ERROR #include #include diff --git a/include/fn/optional.hpp b/include/fn/optional.hpp index 77b585d2..59e96e04 100644 --- a/include/fn/optional.hpp +++ b/include/fn/optional.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_OPTIONAL -#define INCLUDE_FUNCTIONAL_OPTIONAL +#ifndef INCLUDE_FN_OPTIONAL +#define INCLUDE_FN_OPTIONAL #include #include diff --git a/include/fn/or_else.hpp b/include/fn/or_else.hpp index 51d1e606..9935fef5 100644 --- a/include/fn/or_else.hpp +++ b/include/fn/or_else.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_OR_ELSE -#define INCLUDE_FUNCTIONAL_OR_ELSE +#ifndef INCLUDE_FN_OR_ELSE +#define INCLUDE_FN_OR_ELSE #include #include diff --git a/include/fn/pack.hpp b/include/fn/pack.hpp index 6e0102ac..c2b918ff 100644 --- a/include/fn/pack.hpp +++ b/include/fn/pack.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_PACK -#define INCLUDE_FUNCTIONAL_PACK +#ifndef INCLUDE_FN_PACK +#define INCLUDE_FN_PACK #include #include diff --git a/include/fn/recover.hpp b/include/fn/recover.hpp index bf274f3c..78551b19 100644 --- a/include/fn/recover.hpp +++ b/include/fn/recover.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_RECOVER -#define INCLUDE_FUNCTIONAL_RECOVER +#ifndef INCLUDE_FN_RECOVER +#define INCLUDE_FN_RECOVER #include #include diff --git a/include/fn/sum.hpp b/include/fn/sum.hpp index ca0620c6..e092e53b 100644 --- a/include/fn/sum.hpp +++ b/include/fn/sum.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_SUM -#define INCLUDE_FUNCTIONAL_SUM +#ifndef INCLUDE_FN_SUM +#define INCLUDE_FN_SUM #include #include diff --git a/include/fn/transform.hpp b/include/fn/transform.hpp index e029e8d4..f4643d45 100644 --- a/include/fn/transform.hpp +++ b/include/fn/transform.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_TRANSFORM -#define INCLUDE_FUNCTIONAL_TRANSFORM +#ifndef INCLUDE_FN_TRANSFORM +#define INCLUDE_FN_TRANSFORM #include #include diff --git a/include/fn/transform_error.hpp b/include/fn/transform_error.hpp index a4176ee6..1b4c76b9 100644 --- a/include/fn/transform_error.hpp +++ b/include/fn/transform_error.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_TRANSFORM_ERROR -#define INCLUDE_FUNCTIONAL_TRANSFORM_ERROR +#ifndef INCLUDE_FN_TRANSFORM_ERROR +#define INCLUDE_FN_TRANSFORM_ERROR #include #include diff --git a/include/fn/utility.hpp b/include/fn/utility.hpp index 758196b0..f0e026a4 100644 --- a/include/fn/utility.hpp +++ b/include/fn/utility.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_UTILITY -#define INCLUDE_FUNCTIONAL_UTILITY +#ifndef INCLUDE_FN_UTILITY +#define INCLUDE_FN_UTILITY #include #include diff --git a/include/fn/value_or.hpp b/include/fn/value_or.hpp index b0316751..d2747fec 100644 --- a/include/fn/value_or.hpp +++ b/include/fn/value_or.hpp @@ -3,8 +3,8 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC -#ifndef INCLUDE_FUNCTIONAL_OR_ELSE -#define INCLUDE_FUNCTIONAL_OR_ELSE +#ifndef INCLUDE_FN_OR_ELSE +#define INCLUDE_FN_OR_ELSE #include #include diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp new file mode 100644 index 00000000..1406b8ce --- /dev/null +++ b/include/pfn/expected.hpp @@ -0,0 +1,1480 @@ +// Copyright (c) 2025 Bronek Kozicki +// +// Distributed under the ISC License. See accompanying file LICENSE.md +// or copy at https://opensource.org/licenses/ISC + +#ifndef INCLUDE_PFN_EXPECTED +#define INCLUDE_PFN_EXPECTED + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef FWD +#pragma push_macro("FWD") +#define INCLUDE_PFN_EXPECTED__POP_FWD +#undef FWD +#endif + +// Also defined in fn/detail/fwd_macro.hpp but pfn/* headers are standalone +#define FWD(...) static_cast(__VA_ARGS__) + +#ifdef ASSERT +#pragma push_macro("ASSERT") +#define INCLUDE_PFN_EXPECTED__POP_ASSERT +#undef ASSERT +#endif + +// LIBFN_ASSERT is a customization point for the user +#ifdef LIBFN_ASSERT +#define ASSERT(...) LIBFN_ASSERT(__VA_ARGS__) +#else +#define ASSERT(...) assert((__VA_ARGS__) == true) +#endif + +namespace pfn { + +template class bad_expected_access; + +template <> class bad_expected_access : public ::std::exception { +protected: + bad_expected_access() noexcept = default; + bad_expected_access(bad_expected_access const &) noexcept = default; + bad_expected_access(bad_expected_access &&) noexcept = default; + bad_expected_access &operator=(bad_expected_access const &) noexcept = default; + bad_expected_access &operator=(bad_expected_access &&) noexcept = default; + ~bad_expected_access() noexcept = default; + +public: + [[nodiscard]] char const *what() const noexcept override + { + static char const msg_[] = "bad access to expected without expected value"; + return msg_; + } +}; + +template class bad_expected_access : public bad_expected_access { +public: + explicit bad_expected_access(E e) : e_(std::move(e)) {} + [[nodiscard]] char const *what() const noexcept override { return bad_expected_access::what(); }; + E &error() & noexcept { return e_; } + E const &error() const & noexcept { return e_; } + E &&error() && noexcept { return ::std::move(e_); } + E const &&error() const && noexcept { return ::std::move(e_); } + +private: + E e_; +}; + +struct unexpect_t { + explicit unexpect_t() = default; +}; +constexpr inline unexpect_t unexpect{}; + +template class unexpected; + +namespace detail { +template constexpr bool _is_some_unexpected = false; +template constexpr bool _is_some_unexpected<::pfn::unexpected> = true; + +template +constexpr bool _is_valid_unexpected = // + ::std::is_object_v // i.e. not a reference or void or function + && not ::std::is_array_v // + && not _is_some_unexpected // + && not ::std::is_const_v // + && not ::std::is_volatile_v; +} // namespace detail + +template class unexpected { + static_assert(detail::_is_valid_unexpected); + +public: + constexpr unexpected(unexpected const &) = default; + constexpr unexpected(unexpected &&) = default; + + template + constexpr explicit unexpected(Err &&e) noexcept(::std::is_nothrow_constructible_v) + requires(not ::std::is_same_v<::std::remove_cvref_t, unexpected> && // + not ::std::is_same_v<::std::remove_cvref_t, ::std::in_place_t> && // + ::std::is_constructible_v) + : e_(FWD(e)) + { + } + + template + constexpr explicit unexpected(::std::in_place_t, Args &&...a) noexcept(::std::is_nothrow_constructible_v) + requires ::std::is_constructible_v + : e_(FWD(a)...) + { + } + + template + constexpr explicit unexpected(::std::in_place_t, ::std::initializer_list i, Args &&...a) noexcept( + ::std::is_nothrow_constructible_v &, Args...>) + requires ::std::is_constructible_v &, Args...> + : e_(i, FWD(a)...) + { + } + + constexpr unexpected &operator=(unexpected const &) = default; + constexpr unexpected &operator=(unexpected &&) = default; + + constexpr E const &error() const & noexcept { return e_; }; + constexpr E &error() & noexcept { return e_; }; + constexpr E const &&error() const && noexcept { return ::std::move(e_); }; + constexpr E &&error() && noexcept { return ::std::move(e_); }; + + constexpr void swap(unexpected &other) noexcept(::std::is_nothrow_swappable_v) + { + static_assert(::std::is_swappable_v); + using ::std::swap; + swap(e_, other.e_); + } + + template constexpr friend bool operator==(unexpected const &x, unexpected const &y) + { + return x.e_ == y.e_; + } + + constexpr friend void swap(unexpected &x, unexpected &y) noexcept(noexcept(x.swap(y))) + requires ::std::is_swappable_v + { + x.swap(y); + } + +private: + E e_; +}; + +template unexpected(E) -> unexpected; + +template class expected; +namespace detail { +template constexpr bool _is_some_expected = false; +template constexpr bool _is_some_expected<::pfn::expected> = true; + +template +constexpr bool _is_valid_expected = // + not ::std::is_reference_v // + && not ::std::is_function_v // + && not ::std::is_same_v<::std::remove_cv_t, ::std::in_place_t> // + && not ::std::is_same_v<::std::remove_cv_t, unexpect_t> // + && not _is_some_unexpected<::std::remove_cv_t> // + && detail::_is_valid_unexpected; +} // namespace detail + +// declare void specialization +template + requires ::std::is_void_v +class expected; + +template class expected { + static_assert(detail::_is_valid_expected); + + template + using _can_convert_detail = ::std::bool_constant< // + not(::std::is_same_v && ::std::is_same_v) // + && ::std::is_constructible_v // + && ::std::is_constructible_v // + && not ::std::is_constructible_v, expected &> // + && not ::std::is_constructible_v, expected> // + && not ::std::is_constructible_v, expected const &> // + && not ::std::is_constructible_v, expected const> // + && (::std::is_same_v> // + || ( // + not ::std::is_constructible_v &> // + && not ::std::is_constructible_v> // + && not ::std::is_constructible_v const &> // + && not ::std::is_constructible_v const> // + && not ::std::is_convertible_v &, T> // + && not ::std::is_convertible_v &&, T> // + && not ::std::is_convertible_v const &, T> // + && not ::std::is_convertible_v const &&, T>))>; + + template using _can_copy_convert = _can_convert_detail; + template using _can_move_convert = _can_convert_detail; + template friend class expected; + + template + using _can_convert = ::std::bool_constant< // + not ::std::is_same_v<::std::remove_cvref_t, ::std::in_place_t> // + && not ::std::is_same_v<::std::remove_cvref_t, unexpect_t> // LWG4222 + && not ::std::is_same_v> // + && not detail::_is_some_unexpected<::std::remove_cvref_t> // + && ::std::is_constructible_v // + && (not ::std::is_same_v> // + || not detail::_is_some_expected<::std::remove_cvref_t>)>; + + template + using _can_convert_assign = ::std::bool_constant< // + not ::std::is_same_v> // + && not detail::_is_some_unexpected<::std::remove_cvref_t> // + && ::std::is_constructible_v // + && ::std::is_assignable_v // + && (::std::is_nothrow_constructible_v // + || ::std::is_nothrow_move_constructible_v // + || ::std::is_nothrow_move_constructible_v)>; + + // [expected.object.assign] + template + static constexpr void _reinit(New &newval, Old &oldval, Args &&...args) // + noexcept(::std::is_nothrow_constructible_v) + { + if constexpr (::std::is_nothrow_constructible_v) { + ::std::destroy_at(::std::addressof(oldval)); + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } else if constexpr (::std::is_nothrow_move_constructible_v) { + New tmp(::std::forward(args)...); + ::std::destroy_at(::std::addressof(oldval)); + ::std::construct_at(::std::addressof(newval), std::move(tmp)); + } else { + Old tmp(std::move(oldval)); + ::std::destroy_at(::std::addressof(oldval)); + try { + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } catch (...) { + ::std::construct_at(::std::addressof(oldval), std::move(tmp)); + throw; + } + } + } + + static constexpr void _swap(expected &lhs, expected &rhs) // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_constructible_v) + { + if constexpr (::std::is_nothrow_move_constructible_v) { + E tmp(::std::move(rhs.e_)); + ::std::destroy_at(::std::addressof(rhs.e_)); + try { + ::std::construct_at(::std::addressof(rhs.v_), ::std::move(lhs.v_)); + ::std::destroy_at(::std::addressof(lhs.v_)); + ::std::construct_at(::std::addressof(lhs.e_), ::std::move(tmp)); + } catch (...) { + ::std::construct_at(::std::addressof(rhs.e_), ::std::move(tmp)); + throw; + } + } else { + T tmp(::std::move(lhs.v_)); + ::std::destroy_at(::std::addressof(lhs.v_)); + try { + ::std::construct_at(::std::addressof(lhs.e_), ::std::move(rhs.e_)); + ::std::destroy_at(::std::addressof(rhs.e_)); + ::std::construct_at(::std::addressof(rhs.v_), ::std::move(tmp)); + } catch (...) { + ::std::construct_at(::std::addressof(lhs.v_), ::std::move(tmp)); + throw; + } + } + lhs.set_ = false; + rhs.set_ = true; + } + + template + static constexpr auto _and_then(Self &&self, Fn &&fn) // + noexcept(::std::is_nothrow_invocable_v + && ::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v) + { + using result_t = ::std::remove_cvref_t<::std::invoke_result_t>; + static_assert(detail::_is_some_expected); + static_assert(::std::is_same_v::error_type>); + if (self.has_value()) { + return ::std::invoke(FWD(fn), FWD(self).value()); + } + return result_t(unexpect, FWD(self).error()); + } + + template + static constexpr auto _or_else(Self &&self, Fn &&fn) // + noexcept(::std::is_nothrow_invocable_v + && ::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v) + { + using result_t = ::std::remove_cvref_t<::std::invoke_result_t>; + static_assert(detail::_is_some_expected); + static_assert(::std::is_same_v::value_type>); + if (self.has_value()) { + return result_t(::std::in_place, FWD(self).value()); + } + return ::std::invoke(FWD(fn), FWD(self).error()); + } + + template + static constexpr auto _transform(Self &&self, Fn &&fn) // + noexcept(::std::is_nothrow_invocable_v + && ::std::is_nothrow_constructible_v + && (::std::is_void_v<::std::invoke_result_t> + || ::std::is_nothrow_constructible_v< + ::std::remove_cv_t<::std::invoke_result_t>, + ::std::invoke_result_t>)) + requires(::std::is_constructible_v) + { + using value_t = ::std::remove_cv_t<::std::invoke_result_t>; + static_assert(detail::_is_valid_expected); + using result_t = expected; + if (self.has_value()) { + if constexpr (not ::std::is_void_v) { + static_assert(::std::is_constructible_v>); + return result_t(::std::in_place, ::std::invoke(FWD(fn), FWD(self).value())); + } else { + ::std::invoke(FWD(fn), FWD(self).value()); + return result_t(::std::in_place); + } + } + return result_t(unexpect, FWD(self).error()); + } + + template + static constexpr auto _transform_error(Self &&self, Fn &&fn) // + noexcept(::std::is_nothrow_invocable_v + && ::std::is_nothrow_constructible_v + && ::std::is_nothrow_constructible_v< + ::std::remove_cv_t<::std::invoke_result_t>, + ::std::invoke_result_t>) + requires(::std::is_constructible_v) + { + using error_t = ::std::remove_cv_t<::std::invoke_result_t>; + static_assert(detail::_is_valid_unexpected); + static_assert(::std::is_constructible_v>); + using result_t = expected; + if (not self.has_value()) { + return result_t(unexpect, ::std::invoke(FWD(fn), FWD(self).error())); + } + return result_t(::std::in_place, FWD(self).value()); + } + +public: + using value_type = T; + using error_type = E; + using unexpected_type = unexpected; + + template using rebind = expected; + + // [expected.object.cons], constructors + constexpr expected() // + noexcept(::std::is_nothrow_default_constructible_v) // extension + requires ::std::is_default_constructible_v + : v_(), set_(true) + { + } + + constexpr expected(expected const &) = delete; + constexpr expected(expected const &s) // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_copy_constructible_v) // extension + requires(::std::is_copy_constructible_v && ::std::is_copy_constructible_v + && ::std::is_trivially_copy_constructible_v && ::std::is_trivially_copy_constructible_v) + = default; + constexpr expected(expected const &s) // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_copy_constructible_v) // extension + requires(::std::is_copy_constructible_v && ::std::is_copy_constructible_v + && (not ::std::is_trivially_copy_constructible_v || not ::std::is_trivially_copy_constructible_v)) + : set_(s.set_) + { + if (set_) + ::std::construct_at(::std::addressof(v_), s.v_); + else + ::std::construct_at(::std::addressof(e_), s.e_); + } + + constexpr expected(expected &&s) + requires(::std::is_move_constructible_v && ::std::is_move_constructible_v + && ::std::is_trivially_move_constructible_v && ::std::is_trivially_move_constructible_v) + = default; + constexpr expected(expected &&s) // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_move_constructible_v) // required + requires(::std::is_move_constructible_v && ::std::is_move_constructible_v + && (not ::std::is_trivially_move_constructible_v || not ::std::is_trivially_move_constructible_v)) + : set_(s.set_) + { + if (set_) + ::std::construct_at(::std::addressof(v_), ::std::move(s.v_)); + else + ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + } + + template + constexpr explicit(not ::std::is_convertible_v || not ::std::is_convertible_v) + expected(expected const &s) // + noexcept(::std::is_nothrow_constructible_v + && ::std::is_nothrow_constructible_v) // extension + requires(_can_copy_convert::value) + : set_(s.set_) + { + if (set_) + ::std::construct_at(::std::addressof(v_), s.v_); + else + ::std::construct_at(::std::addressof(e_), s.e_); + } + + template + constexpr explicit(not ::std::is_convertible_v || not ::std::is_convertible_v) + expected(expected &&s) // + noexcept(::std::is_nothrow_constructible_v && ::std::is_nothrow_constructible_v) // extension + requires(_can_move_convert::value) + : set_(s.set_) + { + if (set_) + ::std::construct_at(::std::addressof(v_), ::std::move(s.v_)); + else + ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + } + + template > + constexpr explicit(not ::std::is_convertible_v) expected(U &&v) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires(_can_convert::value) + : v_(FWD(v)), set_(true) + { + } + + template + constexpr explicit(!::std::is_convertible_v) expected(unexpected const &g) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires(::std::is_constructible_v) + : e_(::std::forward(g.error())), set_(false) + { + } + + template + constexpr explicit(!::std::is_convertible_v) expected(unexpected &&g) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires(::std::is_constructible_v) + : e_(::std::forward(g.error())), set_(false) + { + } + + template + constexpr explicit expected(::std::in_place_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires ::std::is_constructible_v + : v_(FWD(a)...), set_(true) + { + } + template + constexpr explicit expected(::std::in_place_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) // extension + requires ::std::is_constructible_v &, Args...> + : v_(il, FWD(a)...), set_(true) + { + } + template + constexpr explicit expected(unexpect_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires ::std::is_constructible_v + : e_(FWD(a)...), set_(false) + { + } + template + constexpr explicit expected(unexpect_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) // extension + requires ::std::is_constructible_v &, Args...> + : e_(il, FWD(a)...), set_(false) + { + } + + // [expected.object.dtor], destructor + constexpr ~expected() noexcept + requires(::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) + = default; + constexpr ~expected() // + requires(::std::is_trivially_destructible_v && not ::std::is_trivially_destructible_v) + { + if (not set_) + ::std::destroy_at(::std::addressof(e_)); + // else T is trivially destructible, no need to do anything + } + constexpr ~expected() // + requires(not ::std::is_trivially_destructible_v && ::std::is_trivially_destructible_v) + { + if (set_) + ::std::destroy_at(::std::addressof(v_)); + // else E is trivially destructible, no need to do anything + } + constexpr ~expected() // + requires(not ::std::is_trivially_destructible_v && not ::std::is_trivially_destructible_v) + { + if (set_) + ::std::destroy_at(::std::addressof(v_)); + else + ::std::destroy_at(::std::addressof(e_)); + } + + // [expected.object.assign], assignment + constexpr expected &operator=(expected const &) = delete; + constexpr expected &operator=(expected const &s) // + noexcept(::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v + && ::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v) // extension + requires(::std::is_copy_assignable_v && ::std::is_copy_constructible_v && ::std::is_copy_assignable_v + && ::std::is_copy_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) + { + if (set_ && s.set_) { + v_ = s.v_; + } else if (set_) { + _reinit(e_, v_, s.e_); + set_ = false; + } else if (s.set_) { + _reinit(v_, e_, s.v_); + set_ = true; + } else { + e_ = s.e_; + } + return *this; + } + + constexpr expected &operator=(expected &&s) // + noexcept(::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v + && ::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v) // required + requires(::std::is_move_constructible_v && ::std::is_move_assignable_v && ::std::is_move_constructible_v + && ::std::is_move_assignable_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) + { + if (set_ && s.set_) { + v_ = ::std::move(s.v_); + } else if (set_) { + _reinit(e_, v_, ::std::move(s.e_)); + set_ = false; + } else if (s.set_) { + _reinit(v_, e_, ::std::move(s.v_)); + set_ = true; + } else { + e_ = ::std::move(s.e_); + } + return *this; + } + + template + constexpr expected &operator=(U &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) // extension + requires(_can_convert_assign::value) + { + if (set_) { + v_ = FWD(s); + } else { + _reinit(v_, e_, FWD(s)); + set_ = true; + } + return *this; + } + + template + constexpr expected &operator=(unexpected const &s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) // extension + requires(::std::is_constructible_v && ::std::is_assignable_v + && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v + || ::std::is_nothrow_move_constructible_v)) + { + if (not set_) { + e_ = ::std::forward(s.error()); + } else { + _reinit(e_, v_, ::std::forward(s.error())); + set_ = false; + } + return *this; + } + + template + constexpr expected &operator=(unexpected &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) // extension + requires(::std::is_constructible_v && ::std::is_assignable_v + && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v + || ::std::is_nothrow_move_constructible_v)) + { + if (not set_) { + e_ = ::std::forward(s.error()); + } else { + _reinit(e_, v_, ::std::forward(s.error())); + set_ = false; + } + return *this; + } + + template + constexpr T &emplace(Args &&...args) noexcept + requires(::std::is_nothrow_constructible_v) + { + if (set_) { + ::std::destroy_at(::std::addressof(v_)); + } else { + ::std::destroy_at(::std::addressof(e_)); + set_ = true; + } + return *::std::construct_at(::std::addressof(v_), std::forward(args)...); + } + + template + constexpr T &emplace(::std::initializer_list il, Args &&...args) noexcept + requires(::std::is_nothrow_constructible_v &, Args...>) + { + if (set_) { + ::std::destroy_at(::std::addressof(v_)); + } else { + ::std::destroy_at(::std::addressof(e_)); + set_ = true; + } + return *::std::construct_at(::std::addressof(v_), il, std::forward(args)...); + } + + // [expected.object.swap], swap + constexpr void + swap(expected &rhs) noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v + && ::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) + requires(::std::is_swappable_v && ::std::is_swappable_v && ::std::is_move_constructible_v + && ::std::is_move_constructible_v + && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) + { + bool const lhset = has_value(); + bool const rhset = rhs.has_value(); + if (lhset == rhset) { + if (lhset) { + using ::std::swap; + swap(v_, rhs.v_); + } else { + using ::std::swap; + swap(e_, rhs.e_); + } + } else { + if (lhset) { + _swap(*this, rhs); + } else { + _swap(rhs, *this); + } + } + } + + constexpr friend void swap(expected &x, expected &y) noexcept(noexcept(x.swap(y))) + requires requires { x.swap(y); } + { + x.swap(y); + } + + // [expected.object.obs], observers + constexpr T const *operator->() const noexcept + { + ASSERT(set_); + return ::std::addressof(v_); + } + constexpr T *operator->() noexcept + { + ASSERT(set_); + return ::std::addressof(v_); + } + constexpr T const &operator*() const & noexcept { return *(this->operator->()); } + constexpr T &operator*() & noexcept { return *(this->operator->()); } + constexpr T const &&operator*() const && noexcept { return ::std::move(*(this->operator->())); } + constexpr T &&operator*() && noexcept { return ::std::move(*(this->operator->())); } + constexpr explicit operator bool() const noexcept { return set_; } + constexpr bool has_value() const noexcept { return set_; } + constexpr T const &value() const & + { + static_assert(::std::is_copy_constructible_v); + if (not set_) + throw bad_expected_access(e_); + return v_; + } + constexpr T &value() & + { + static_assert(::std::is_copy_constructible_v); + if (not set_) + throw bad_expected_access(::std::as_const(e_)); + return v_; + } + constexpr T const &&value() const && + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_constructible_v); + if (not set_) + throw bad_expected_access(::std::move(e_)); + return ::std::move(v_); + } + constexpr T &&value() && + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_constructible_v); + if (not set_) + throw bad_expected_access(::std::move(e_)); + return ::std::move(v_); + } + constexpr E const &error() const & noexcept + { + ASSERT(not set_); + return e_; + } + constexpr E &error() & noexcept + { + ASSERT(not set_); + return e_; + } + constexpr E const &&error() const && noexcept + { + ASSERT(not set_); + return ::std::move(e_); + } + constexpr E &&error() && noexcept + { + ASSERT(not set_); + return ::std::move(e_); + } + + template + constexpr T value_or(U &&v) const & // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_convertible_v) // extension + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_convertible_v); + return set_ ? v_ : static_cast(FWD(v)); + } + template + constexpr T value_or(U &&v) && // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_convertible_v) // extension + { + static_assert(::std::is_move_constructible_v); + static_assert(::std::is_convertible_v); + return set_ ? ::std::move(v_) : static_cast(FWD(v)); + } + + template + constexpr E error_or(G &&e) const & // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_convertible_v) // extension + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_convertible_v); + if (set_) { + return FWD(e); + } + return e_; + } + template + constexpr E error_or(G &&e) && // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_convertible_v) // extension + { + static_assert(::std::is_move_constructible_v); + static_assert(::std::is_convertible_v); + if (set_) { + return FWD(e); + } + return ::std::move(e_); + } + + // [expected.object.monadic], monadic operations + template + constexpr auto and_then(F &&f) & // + noexcept(noexcept(_and_then(*this, FWD(f)))) // extension + -> decltype(_and_then(*this, FWD(f))) + { + return _and_then(*this, FWD(f)); + } + template + constexpr auto and_then(F &&f) && // + noexcept(noexcept(_and_then(::std::move(*this), FWD(f)))) // extension + -> decltype(_and_then(::std::move(*this), FWD(f))) + { + return _and_then(::std::move(*this), FWD(f)); + } + template + constexpr auto and_then(F &&f) const & // + noexcept(noexcept(_and_then(*this, FWD(f)))) // extension + -> decltype(_and_then(*this, FWD(f))) + { + return _and_then(*this, FWD(f)); + } + template + constexpr auto and_then(F &&f) const && // + noexcept(noexcept(_and_then(::std::move(*this), FWD(f)))) // extension + -> decltype(_and_then(::std::move(*this), FWD(f))) + { + return _and_then(::std::move(*this), FWD(f)); + } + + template + constexpr auto or_else(F &&f) & // + noexcept(noexcept(_or_else(*this, FWD(f)))) // extension + -> decltype(_or_else(*this, FWD(f))) + { + return _or_else(*this, FWD(f)); + } + template + constexpr auto or_else(F &&f) && // + noexcept(noexcept(_or_else(::std::move(*this), FWD(f)))) // extension + -> decltype(_or_else(::std::move(*this), FWD(f))) + { + return _or_else(::std::move(*this), FWD(f)); + } + template + constexpr auto or_else(F &&f) const & // + noexcept(noexcept(_or_else(*this, FWD(f)))) // extension + -> decltype(_or_else(*this, FWD(f))) + { + return _or_else(*this, FWD(f)); + } + template + constexpr auto or_else(F &&f) const && // + noexcept(noexcept(_or_else(::std::move(*this), FWD(f)))) // extension + -> decltype(_or_else(::std::move(*this), FWD(f))) + { + return _or_else(::std::move(*this), FWD(f)); + } + + template + constexpr auto transform(F &&f) & // + noexcept(noexcept(_transform(*this, FWD(f)))) // extension + -> decltype(_transform(*this, FWD(f))) + { + return _transform(*this, FWD(f)); + } + template + constexpr auto transform(F &&f) && // + noexcept(noexcept(_transform(::std::move(*this), FWD(f)))) // extension + -> decltype(_transform(::std::move(*this), FWD(f))) + { + return _transform(::std::move(*this), FWD(f)); + } + template + constexpr auto transform(F &&f) const & // + noexcept(noexcept(_transform(*this, FWD(f)))) // extension + -> decltype(_transform(*this, FWD(f))) + { + return _transform(*this, FWD(f)); + } + template + constexpr auto transform(F &&f) const && // + noexcept(noexcept(_transform(::std::move(*this), FWD(f)))) // extension + -> decltype(_transform(::std::move(*this), FWD(f))) + { + return _transform(::std::move(*this), FWD(f)); + } + + template + constexpr auto transform_error(F &&f) & // + noexcept(noexcept(_transform_error(*this, FWD(f)))) // extension + -> decltype(_transform_error(*this, FWD(f))) + { + return _transform_error(*this, FWD(f)); + } + template + constexpr auto transform_error(F &&f) && // + noexcept(noexcept(_transform_error(::std::move(*this), FWD(f)))) // extension + -> decltype(_transform_error(::std::move(*this), FWD(f))) + { + return _transform_error(::std::move(*this), FWD(f)); + } + template + constexpr auto transform_error(F &&f) const & // + noexcept(noexcept(_transform_error(*this, FWD(f)))) // extension + -> decltype(_transform_error(*this, FWD(f))) + { + return _transform_error(*this, FWD(f)); + } + template + constexpr auto transform_error(F &&f) const && // + noexcept(noexcept(_transform_error(::std::move(*this), FWD(f)))) // extension + -> decltype(_transform_error(::std::move(*this), FWD(f))) + { + return _transform_error(::std::move(*this), FWD(f)); + } + + // [expected.object.eq], equality operators + template + requires(not ::std::is_void_v) + constexpr friend bool operator==(expected const &x, expected const &y) // + noexcept(noexcept(static_cast(*x == *y)) && noexcept(static_cast(x.error() == y.error()))) // extension + requires ( // +requires { + { *x == *y } -> std::convertible_to; + } && +requires { + { x.error() == y.error() } -> std::convertible_to; + }) + { + if (x.has_value() != y.has_value()) { + return false; + } else if (x.has_value()) { + return *x == *y; + } else { + return x.error() == y.error(); + } + } + template + requires(not detail::_is_some_expected) + constexpr friend bool operator==(expected const &x, const T2 &v) // + noexcept(noexcept(static_cast(*x == v))) // extension + requires requires { + { *x == v } -> std::convertible_to; + } + { + return x.has_value() && static_cast(*x == v); + } + template + constexpr friend bool operator==(expected const &x, unexpected const &e) // + noexcept(noexcept(static_cast(x.error() == e.error()))) // extension + requires requires { + { x.error() == e.error() } -> std::convertible_to; + } + { + return (not x.has_value()) && static_cast(x.error() == e.error()); + } + +private: + union { + T v_; + E e_; + }; + bool set_; +}; + +template + requires ::std::is_void_v +class expected { + static_assert(detail::_is_valid_unexpected); + + template + using _can_convert_detail = ::std::bool_constant< // + ::std::is_void_v && ::std::is_constructible_v // + && not ::std::is_constructible_v, expected &> // + && not ::std::is_constructible_v, expected> // + && not ::std::is_constructible_v, expected const &> // + && not ::std::is_constructible_v, expected const>>; + + template using _can_copy_convert = _can_convert_detail; + template using _can_move_convert = _can_convert_detail; + template friend class expected; + + template + using _can_convert = ::std::bool_constant< // + not ::std::is_same_v<::std::remove_cvref_t, ::std::in_place_t> // + && not ::std::is_same_v<::std::remove_cvref_t, unexpect_t> // LWG4222 + && not ::std::is_same_v> // + && not detail::_is_some_unexpected<::std::remove_cvref_t> // + && ::std::is_constructible_v // + && (not ::std::is_same_v> // + || not detail::_is_some_expected<::std::remove_cvref_t>)>; + + template + static constexpr auto _and_then(Self &&self, Fn &&fn) // + noexcept(::std::is_nothrow_invocable_v && ::std::is_nothrow_constructible_v) + requires(::std::is_constructible_v) + { + using result_t = ::std::remove_cvref_t<::std::invoke_result_t>; + static_assert(detail::_is_some_expected); + static_assert(::std::is_same_v::error_type>); + if (self.has_value()) { + return ::std::invoke(FWD(fn)); + } + return result_t(unexpect, FWD(self).error()); + } + + template + static constexpr auto _or_else(Self &&self, Fn &&fn) // + noexcept(::std::is_nothrow_invocable_v) + { + using result_t = ::std::remove_cvref_t<::std::invoke_result_t>; + static_assert(detail::_is_some_expected); + static_assert(::std::is_same_v::value_type>); + if (self.has_value()) { + return result_t(::std::in_place); + } + return ::std::invoke(FWD(fn), FWD(self).error()); + } + + template + static constexpr auto _transform(Self &&self, Fn &&fn) // + noexcept(::std::is_nothrow_invocable_v && ::std::is_nothrow_constructible_v + && (::std::is_void_v<::std::invoke_result_t> + || ::std::is_nothrow_constructible_v<::std::remove_cv_t<::std::invoke_result_t>, + ::std::invoke_result_t>)) + requires(::std::is_constructible_v) + { + using value_t = ::std::remove_cv_t<::std::invoke_result_t>; + static_assert(detail::_is_valid_expected); + using result_t = expected; + if (self.has_value()) { + if constexpr (not ::std::is_void_v) { + static_assert(::std::is_constructible_v>); + return result_t(::std::in_place, ::std::invoke(FWD(fn))); + } else { + ::std::invoke(FWD(fn)); + return result_t(::std::in_place); + } + } + return result_t(unexpect, FWD(self).error()); + } + + template + static constexpr auto _transform_error(Self &&self, Fn &&fn) // + noexcept(::std::is_nothrow_invocable_v + && ::std::is_nothrow_constructible_v< + ::std::remove_cv_t<::std::invoke_result_t>, + ::std::invoke_result_t>) + { + using error_t = ::std::remove_cv_t<::std::invoke_result_t>; + static_assert(detail::_is_valid_unexpected); + static_assert(::std::is_constructible_v>); + using result_t = expected; + if (not self.has_value()) { + return result_t(unexpect, ::std::invoke(FWD(fn), FWD(self).error())); + } + return result_t(::std::in_place); + } + +public: + using value_type = T; + using error_type = E; + using unexpected_type = unexpected; + + template using rebind = expected; + + // [expected.void.cons], constructors + constexpr expected() noexcept : d_(), set_(true) {} + constexpr expected(expected const &) = delete; + constexpr expected(expected const &) // + requires(::std::is_copy_constructible_v && ::std::is_trivially_copy_constructible_v) // + = default; + constexpr expected(expected const &s) // + noexcept(::std::is_nothrow_copy_constructible_v) // extension + requires(::std::is_copy_constructible_v && not ::std::is_trivially_copy_constructible_v) // + : d_(), set_(s.set_) + { + if (not set_) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), s.e_); + } + } + + constexpr expected(expected &&s) // + requires(::std::is_move_constructible_v && ::std::is_trivially_move_constructible_v) // + = default; + constexpr expected(expected &&s) // + noexcept(::std::is_nothrow_move_constructible_v) // required + requires(::std::is_move_constructible_v && not ::std::is_trivially_move_constructible_v) + : d_(), set_(s.set_) + { + if (not set_) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + } + } + + template + constexpr explicit(not ::std::is_convertible_v) expected(expected const &s) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires(_can_copy_convert::value) + : d_(), set_(s.set_) + { + if (not set_) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), s.e_); + } + } + template + constexpr explicit(not ::std::is_convertible_v) expected(expected &&s) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires(_can_move_convert::value) + : d_(), set_(s.set_) + { + if (not set_) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + } + } + + template + constexpr explicit(!::std::is_convertible_v) expected(unexpected const &g) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires(::std::is_constructible_v) + : e_(::std::forward(g.error())), set_(false) + { + } + template + constexpr explicit(!::std::is_convertible_v) expected(unexpected &&g) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires(::std::is_constructible_v) + : e_(::std::forward(g.error())), set_(false) + { + } + + constexpr explicit expected(::std::in_place_t) noexcept : d_(), set_(true) {} + + template + constexpr explicit expected(unexpect_t, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v) // extension + requires ::std::is_constructible_v + : e_(FWD(a)...), set_(false) + { + } + template + constexpr explicit expected(unexpect_t, ::std::initializer_list il, Args &&...a) // + noexcept(::std::is_nothrow_constructible_v &, Args...>) // extension + requires ::std::is_constructible_v &, Args...> + : e_(il, FWD(a)...), set_(false) + { + } + + // [expected.void.dtor], destructor + constexpr ~expected() noexcept + requires(::std::is_trivially_destructible_v) + = default; + constexpr ~expected() // + requires(not ::std::is_trivially_destructible_v) + { + if (not set_) + ::std::destroy_at(::std::addressof(e_)); + else + ::std::destroy_at(::std::addressof(d_)); + } + + // [expected.void.assign], assignment + constexpr expected &operator=(expected const &) = delete; + constexpr expected &operator=(expected const &s) // + noexcept(::std::is_nothrow_copy_assignable_v && ::std::is_nothrow_copy_constructible_v) // extension + requires(::std::is_copy_assignable_v && ::std::is_copy_constructible_v) + { + if (set_ && s.set_) { + ; + } else if (set_) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), s.e_); + set_ = false; + } else if (s.set_) { + ::std::destroy_at(::std::addressof(e_)); + ::std::construct_at(::std::addressof(d_)); + set_ = true; + } else { + e_ = s.e_; + } + return *this; + } + + constexpr expected &operator=(expected &&s) // + noexcept(::std::is_nothrow_move_assignable_v && ::std::is_nothrow_move_constructible_v) // required + requires(::std::is_move_constructible_v && ::std::is_move_assignable_v) + { + if (set_ && s.set_) { + ; + } else if (set_) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), ::std::move(s.e_)); + set_ = false; + } else if (s.set_) { + ::std::destroy_at(::std::addressof(e_)); + ::std::construct_at(::std::addressof(d_)); + set_ = true; + } else { + e_ = ::std::move(s.e_); + } + return *this; + } + + template + constexpr expected &operator=(unexpected const &s) // + noexcept(::std::is_nothrow_assignable_v + && ::std::is_nothrow_constructible_v) // extension + requires(::std::is_constructible_v && ::std::is_assignable_v) + { + if (set_) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), ::std::forward(s.error())); + set_ = false; + } else { + e_ = ::std::forward(s.error()); + } + return *this; + } + + template + constexpr expected &operator=(unexpected &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension + requires(::std::is_constructible_v && ::std::is_assignable_v) + { + if (set_) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), ::std::forward(s.error())); + set_ = false; + } else { + e_ = ::std::forward(s.error()); + } + return *this; + } + + constexpr void emplace() noexcept + { + if (not set_) { + ::std::destroy_at(::std::addressof(e_)); + ::std::construct_at(::std::addressof(d_)); + set_ = true; + } + } + + // [expected.void.swap], swap + constexpr void swap(expected &rhs) // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_swappable_v) + requires(::std::is_swappable_v && ::std::is_move_constructible_v) + { + bool const lhset = has_value(); + bool const rhset = rhs.has_value(); + if (lhset == rhset) { + if (not lhset) { + using ::std::swap; + swap(e_, rhs.e_); + } + } else { + if (lhset) { + ::std::destroy_at(::std::addressof(d_)); + ::std::construct_at(::std::addressof(e_), ::std::move(rhs.e_)); + ::std::destroy_at(::std::addressof(rhs.e_)); + ::std::construct_at(::std::addressof(rhs.d_)); + set_ = false; + rhs.set_ = true; + } else { + rhs.swap(*this); + } + } + } + + constexpr friend void swap(expected &x, expected &y) noexcept(noexcept(x.swap(y))) + requires requires { x.swap(y); } + { + x.swap(y); + } + + // [expected.void.obs], observers + constexpr explicit operator bool() const noexcept { return set_; } + constexpr bool has_value() const noexcept { return set_; } + constexpr void operator*() const noexcept { ASSERT(set_); } + constexpr void value() const & + { + static_assert(::std::is_copy_constructible_v); + if (not set_) + throw bad_expected_access(e_); + } + constexpr void value() && + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_move_constructible_v); + if (not set_) + throw bad_expected_access(::std::move(e_)); + } + constexpr E const &error() const & noexcept + { + ASSERT(not set_); + return e_; + } + constexpr E &error() & noexcept + { + ASSERT(not set_); + return e_; + } + constexpr E const &&error() const && noexcept + { + ASSERT(not set_); + return ::std::move(e_); + } + constexpr E &&error() && noexcept + { + ASSERT(not set_); + return ::std::move(e_); + } + + template + constexpr E error_or(G &&e) const & // + noexcept(::std::is_nothrow_copy_constructible_v && ::std::is_nothrow_convertible_v) // extension + { + static_assert(::std::is_copy_constructible_v); + static_assert(::std::is_convertible_v); + if (set_) { + return FWD(e); + } + return e_; + } + template + constexpr E error_or(G &&e) && // + noexcept(::std::is_nothrow_move_constructible_v && ::std::is_nothrow_convertible_v) // extension + { + static_assert(::std::is_move_constructible_v); + static_assert(::std::is_convertible_v); + if (set_) { + return FWD(e); + } + return ::std::move(e_); + } + + // [expected.void.monadic], monadic operations + template + constexpr auto and_then(F &&f) & // + noexcept(noexcept(_and_then(*this, FWD(f)))) // extension + -> decltype(_and_then(*this, FWD(f))) + { + return _and_then(*this, FWD(f)); + } + template + constexpr auto and_then(F &&f) && // + noexcept(noexcept(_and_then(::std::move(*this), FWD(f)))) // extension + -> decltype(_and_then(::std::move(*this), FWD(f))) + { + return _and_then(::std::move(*this), FWD(f)); + } + template + constexpr auto and_then(F &&f) const & // + noexcept(noexcept(_and_then(*this, FWD(f)))) // extension + -> decltype(_and_then(*this, FWD(f))) + { + return _and_then(*this, FWD(f)); + } + template + constexpr auto and_then(F &&f) const && // + noexcept(noexcept(_and_then(::std::move(*this), FWD(f)))) // extension + -> decltype(_and_then(::std::move(*this), FWD(f))) + { + return _and_then(::std::move(*this), FWD(f)); + } + + template + constexpr auto or_else(F &&f) & // + noexcept(noexcept(_or_else(*this, FWD(f)))) // extension + -> decltype(_or_else(*this, FWD(f))) + { + return _or_else(*this, FWD(f)); + } + template + constexpr auto or_else(F &&f) && // + noexcept(noexcept(_or_else(::std::move(*this), FWD(f)))) // extension + -> decltype(_or_else(::std::move(*this), FWD(f))) + { + return _or_else(::std::move(*this), FWD(f)); + } + template + constexpr auto or_else(F &&f) const & // + noexcept(noexcept(_or_else(*this, FWD(f)))) // extension + -> decltype(_or_else(*this, FWD(f))) + { + return _or_else(*this, FWD(f)); + } + template + constexpr auto or_else(F &&f) const && // + noexcept(noexcept(_or_else(::std::move(*this), FWD(f)))) // extension + -> decltype(_or_else(::std::move(*this), FWD(f))) + { + return _or_else(::std::move(*this), FWD(f)); + } + + template + constexpr auto transform(F &&f) & // + noexcept(noexcept(_transform(*this, FWD(f)))) // extension + -> decltype(_transform(*this, FWD(f))) + { + return _transform(*this, FWD(f)); + } + template + constexpr auto transform(F &&f) && // + noexcept(noexcept(_transform(::std::move(*this), FWD(f)))) // extension + -> decltype(_transform(::std::move(*this), FWD(f))) + { + return _transform(::std::move(*this), FWD(f)); + } + template + constexpr auto transform(F &&f) const & // + noexcept(noexcept(_transform(*this, FWD(f)))) // extension + -> decltype(_transform(*this, FWD(f))) + { + return _transform(*this, FWD(f)); + } + template + constexpr auto transform(F &&f) const && // + noexcept(noexcept(_transform(::std::move(*this), FWD(f)))) // extension + -> decltype(_transform(::std::move(*this), FWD(f))) + { + return _transform(::std::move(*this), FWD(f)); + } + + template + constexpr auto transform_error(F &&f) & // + noexcept(noexcept(_transform_error(*this, FWD(f)))) // extension + -> decltype(_transform_error(*this, FWD(f))) + { + return _transform_error(*this, FWD(f)); + } + template + constexpr auto transform_error(F &&f) && // + noexcept(noexcept(_transform_error(::std::move(*this), FWD(f)))) // extension + -> decltype(_transform_error(::std::move(*this), FWD(f))) + { + return _transform_error(::std::move(*this), FWD(f)); + } + template + constexpr auto transform_error(F &&f) const & // + noexcept(noexcept(_transform_error(*this, FWD(f)))) // extension + -> decltype(_transform_error(*this, FWD(f))) + { + return _transform_error(*this, FWD(f)); + } + template + constexpr auto transform_error(F &&f) const && // + noexcept(noexcept(_transform_error(::std::move(*this), FWD(f)))) // extension + -> decltype(_transform_error(::std::move(*this), FWD(f))) + { + return _transform_error(::std::move(*this), FWD(f)); + } + + // [expected.void.eq], equality operators + template + requires(::std::is_void_v) + constexpr friend bool operator==(expected const &x, expected const &y) // + noexcept(noexcept(static_cast((x.error() == y.error())))) // extension + requires requires { + { x.error() == y.error() } -> std::convertible_to; + } + { + if (x.has_value() != y.has_value()) { + return false; + } else if (x.has_value()) { + return true; + } else { + return x.error() == y.error(); + } + } + template + constexpr friend bool operator==(expected const &x, unexpected const &e) // + noexcept(noexcept(static_cast(x.error() == e.error()))) // extension + requires requires { + { x.error() == e.error() } -> std::convertible_to; + } + { + return (not x.has_value()) && static_cast(x.error() == e.error()); + } + +private: + struct dummy final { + constexpr dummy() noexcept = default; + constexpr ~dummy() noexcept = default; + }; + + union { + [[no_unique_address]] dummy d_; + E e_; + }; + bool set_; +}; + +} // namespace pfn + +#undef ASSERT + +#ifdef INCLUDE_PFN_EXPECTED__POP_ASSERT +#pragma pop_macro("ASSERT") +#endif + +#undef FWD + +#ifdef INCLUDE_PFN_EXPECTED__POP_FWD +#pragma pop_macro("FWD") +#endif + +#endif // INCLUDE_PFN_EXPECTED diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2e46e376..5534c2b8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,17 +2,105 @@ cmake_minimum_required(VERSION 3.25) project(tests) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Pls keep the filenames sorted set(TESTS_UTIL_SOURCES util/static_check.hpp + util/helper_types.hpp ) add_library(tests_util INTERFACE ${TESTS_UTIL_SOURCES}) +set_property(TARGET tests_util PROPERTY CXX_STANDARD 23) target_include_directories(tests_util INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(tests_util INTERFACE include_fn) +# Generate sentinel target for each individual header, as a basic sanity check +foreach(mode 23) + foreach(source IN ITEMS ${TESTS_UTIL_SOURCES}) + string(REGEX REPLACE "^(util)/([^\.]+)\.hpp$" "\\1_\\2" root_name ${source}) + set(target "sentinel_${root_name}_cxx${mode}") + + create_target_for_file( + NAME "${target}" + SOURCE "${source}" + NEW_SOURCE "#include <${source}>\nint main() {}\n" + SOURCE_ROOT "${CMAKE_BINARY_DIR}/sentinel" + DEPENDENCIES include_fn tests_util + ) + append_compilation_options("${target}" WARNINGS) + add_dependencies("cxx${mode}" "${target}") + set_property(TARGET "${target}" PROPERTY CXX_STANDARD "${mode}") + target_compile_definitions("${target}" PRIVATE LIBFN_MODE=${mode}) + + unset(target) + unset(root_name) + endforeach() +endforeach() + +### tests/pfn + +set(TESTS_PFN_SOURCES + pfn/expected.cpp +) + +include(TargetGenerator) +include(CompilationOptions) + +# Generate separate target for each individual test source +foreach(mode 20 23) + foreach(source IN ITEMS ${TESTS_PFN_SOURCES}) + string(REGEX REPLACE "^(pfn)/(detail|)/?([^\.]+)\.cpp$" "\\1_\\2\\3" root_name ${source}) + set(target "tests_${root_name}_cxx${mode}") + + create_target_for_file( + NAME "${target}" + SOURCE "${source}" + DEPENDENCIES include_pfn tests_util Catch2::Catch2WithMain + ) + append_compilation_options("${target}" WARNINGS OPTIMIZATION) + add_dependencies("cxx${mode}" "${target}") + set_property(TARGET "${target}" PROPERTY CXX_STANDARD "${mode}") + target_compile_definitions("${target}" PRIVATE LIBFN_MODE=${mode}) + + add_test( + NAME "${target}" + COMMAND "${target}" -r console + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + set_property(TEST "${target}" PROPERTY LABELS tests_pfn "cxx${mode}" "${root_name}") + + unset(target) + + # pfn tests have a "validation" mode, where the unit tests intended for polyfill + # are run against the "real" thing. This validates the tests. + if (mode EQUAL 23) + set(target "tests_${root_name}_validation_cxx${mode}") + + create_target_for_file( + NAME "${target}" + SOURCE "${source}" + DEPENDENCIES include_pfn tests_util Catch2::Catch2WithMain + ) + append_compilation_options("${target}" WARNINGS OPTIMIZATION) + add_dependencies("cxx${mode}" "${target}") + set_property(TARGET "${target}" PROPERTY CXX_STANDARD "${mode}") + target_compile_definitions("${target}" PRIVATE LIBFN_MODE=${mode} PFN_TEST_VALIDATION) + + add_test( + NAME "${target}" + COMMAND "${target}" -r console + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + set_property(TEST "${target}" PROPERTY LABELS tests_pfn "cxx${mode}" "${root_name}") + + unset(target) + endif() + + unset(root_name) + endforeach() +endforeach() + +### tests/fn # Pls keep the filenames sorted set(TESTS_FN_SOURCES @@ -41,38 +129,57 @@ set(TESTS_FN_SOURCES fn/value_or.cpp ) -include(TargetGenerator) - # Generate separate target for each individual test source -foreach(source IN ITEMS ${TESTS_FN_SOURCES}) - string(REGEX REPLACE "^fn/(detail|)/?([^\.]+)\.cpp$" "\\1\\2" root_name ${source}) - setup_target_for_file( - NAME "tests_fn_${root_name}" - SOURCE "${source}" - DEPENDENCIES include_fn tests_util Catch2::Catch2WithMain - COMPILE_OPTIONS -Wall -Wextra -Wpedantic -Wno-missing-braces "$<$:-O0>" - TEST_OPTIONS -r console - TEST_LABELS tests_fn ${root_name} - ADD_TEST - ) - unset(root_name) +# TODO add 20 when we are compatible with C++20 +foreach(mode 23) + foreach(source IN ITEMS ${TESTS_FN_SOURCES}) + string(REGEX REPLACE "^(fn)/(detail|)/?([^\.]+)\.cpp$" "\\1_\\2\\3" root_name ${source}) + set(target "tests_${root_name}_cxx${mode}") + + create_target_for_file( + NAME "${target}" + SOURCE "${source}" + DEPENDENCIES include_fn tests_util Catch2::Catch2WithMain + ) + append_compilation_options("${target}" WARNINGS OPTIMIZATION) + add_dependencies("cxx${mode}" "${target}") + set_property(TARGET "${target}" PROPERTY CXX_STANDARD "${mode}") + target_compile_definitions("${target}" PRIVATE LIBFN_MODE=${mode}) + + add_test( + NAME "${target}" + COMMAND "${target}" -r console + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + set_property(TEST "${target}" PROPERTY LABELS tests_fn "cxx${mode}" "${root_name}") + + unset(target) + unset(root_name) + endforeach() endforeach() -# Pls keep the filenames sorted +# TODO change examples into subproject and switch to add_subdirectory set(TESTS_EXAMPLES_SOURCES examples/simple.cpp ) -foreach(source IN ITEMS ${TESTS_EXAMPLES_SOURCES}) - string(REGEX REPLACE "^examples/([^\.]+)\.cpp$" "\\1" root_name ${source}) - setup_target_for_file( - NAME "tests_examples_${root_name}" - SOURCE "${source}" - DEPENDENCIES include_fn Catch2::Catch2WithMain - COMPILE_OPTIONS -Wall -Wextra -Wpedantic -Wno-missing-braces - TEST_OPTIONS -r console - TEST_LABELS tests_examples ${root_name} - ADD_TEST +# TODO add 20 when we are compatible with C++20 +foreach(mode 23) + set(target "tests_examples_cxx${mode}") + + add_executable("${target}" ${TESTS_EXAMPLES_SOURCES}) + target_link_libraries("${target}" include_fn Catch2::Catch2WithMain) + append_compilation_options("${target}" WARNINGS) + add_dependencies("cxx${mode}" "${target}") + set_property(TARGET "${target}" PROPERTY CXX_STANDARD "${mode}") + target_compile_definitions("${target}" PRIVATE LIBFN_MODE=${mode}) + + add_test( + NAME "${target}" + COMMAND "${target}" -r console + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) - unset(root_name) + set_property(TEST "${target}" PROPERTY LABELS tests_examples "cxx${mode}") + + unset(target) endforeach() diff --git a/tests/fn/choice.cpp b/tests/fn/choice.cpp index acaa6118..3a6d1959 100644 --- a/tests/fn/choice.cpp +++ b/tests/fn/choice.cpp @@ -4,8 +4,11 @@ // or copy at https://opensource.org/licenses/ISC #include + #include +#include + #include #include @@ -40,18 +43,19 @@ TEST_CASE("choice non-monadic functionality", "[choice]") static_assert(std::same_as, typename choice::value_type>); static_assert(std::same_as, typename choice::value_type>); - using type = choice; - using value_type = fn::sum; + using type = fn::choice; + using value_type = fn::sum; static_assert(std::same_as().value())>); static_assert(std::same_as().value())>); static_assert(std::same_as().value())>); static_assert(std::same_as().value())>); - type s{42}; + type s{helper{1}}; + s.value().get_ptr()->v = 42; constexpr auto fn = fn::overload{ [](auto &&) -> int { throw 0; }, // - [](int &i) -> int { return i + 1; }, [](int const &i) -> int { return i + 2; }, - [](int &&i) -> int { return i + 3; }, [](int const &&i) -> int { return i + 4; }, + [](helper &o) -> int { return o.v + 1; }, [](helper const &o) -> int { return o.v + 2; }, + [](helper &&o) -> int { return o.v + 3; }, [](helper const &&o) -> int { return o.v + 4; }, }; static_assert(std::same_as); CHECK((s.value().invoke(fn)) == 43); @@ -146,30 +150,165 @@ TEST_CASE("choice non-monadic functionality", "[choice]") static_assert(c.invoke([](auto &&a) -> bool { return a.size() == 3 && a[0] == 3 && a[1] == 14 && a[2] == 15; })); } - WHEN("move from rvalue") + WHEN("constexpr move from rvalue") { - using T = fn::choice; + using T = fn::choice; constexpr auto fn = [](auto i) constexpr noexcept -> T { return {std::move(i)}; }; constexpr auto a = fn(true); static_assert(std::is_same_v); static_assert(a.has_value()); + static_assert(a.value() == fn::sum{true}); - constexpr auto b = fn(12); + constexpr auto b = fn(helper{{0.5, 2.0}, 19}); static_assert(std::is_same_v); - static_assert(b.has_value()); + static_assert(b.has_value()); + static_assert(b.value().get_ptr()->v == 19 * helper::from_rval); } - WHEN("copy from lvalue") + WHEN("move from rvalue") + { + using T = fn::choice; + constexpr auto fn = [](auto i) noexcept -> T { return {std::move(i)}; }; + auto const a = fn(helper{9}); + static_assert(std::is_same_v); + CHECK(a.has_value()); + CHECK(a.value().get_ptr()->v == 9 * helper::from_rval); + + auto b = fn(true); + static_assert(std::is_same_v); + CHECK(b.has_value()); + CHECK(b.value() == fn::sum{true}); + } + + WHEN("constexpr move from const rvalue") { - using T = fn::choice; + using T = fn::choice; + constexpr auto fn = [](auto const i) constexpr noexcept -> T { return {std::move(i)}; }; + constexpr auto a = fn(true); + static_assert(std::is_same_v); + static_assert(a.has_value()); + static_assert(a.value() == fn::sum{true}); + + constexpr auto b = fn(helper{{0.5, 2.0}, 17}); + static_assert(std::is_same_v); + static_assert(b.has_value()); + static_assert(b.value().get_ptr()->v == 17 * helper::from_rval_const); + } + + WHEN("move from const rvalue") + { + using T = fn::choice; + constexpr auto fn = [](auto const i) noexcept -> T { return {std::move(i)}; }; + auto const a = fn(helper{7}); + static_assert(std::is_same_v); + CHECK(a.has_value()); + CHECK(a.value().get_ptr()->v == 7 * helper::from_rval_const); + + auto b = fn(true); + static_assert(std::is_same_v); + CHECK(b.has_value()); + CHECK(b.value() == fn::sum{true}); + } + + WHEN("constexpr copy from lvalue") + { + using T = fn::choice; constexpr auto fn = [](auto i) constexpr noexcept -> T { return {i}; }; constexpr auto a = fn(true); static_assert(std::is_same_v); static_assert(a.has_value()); + static_assert(a.value() == fn::sum{true}); - constexpr auto b = fn(12); + constexpr auto b = fn(helper{{0.5, 2.0}, 17}); static_assert(std::is_same_v); - static_assert(b.has_value()); + static_assert(b.has_value()); + static_assert(b.value().get_ptr()->v == 17 * helper::from_lval); + } + + WHEN("copy from lvalue") + { + using T = fn::choice; + constexpr auto fn = [](auto i) noexcept -> T { return {i}; }; + auto const a = fn(true); + static_assert(std::is_same_v); + CHECK(a.has_value()); + CHECK(a.value() == fn::sum{true}); + + auto b = fn(helper{13}); + static_assert(std::is_same_v); + CHECK(b.has_value()); + CHECK(b.value().get_ptr()->v == 13 * helper::from_lval); + } + + WHEN("constexpr copy from const lvalue") + { + using T = fn::choice; + constexpr auto fn = [](auto const i) constexpr noexcept -> T { return {i}; }; + constexpr auto a = fn(true); + static_assert(std::is_same_v); + static_assert(a.has_value()); + static_assert(a.value() == fn::sum{true}); + + constexpr auto b = fn(helper{{0.5, 2.0}, 15}); + static_assert(std::is_same_v); + static_assert(b.has_value()); + static_assert(b.value().get_ptr()->v == 15 * helper::from_lval_const); + } + + WHEN("copy from const lvalue") + { + using T = fn::choice; + constexpr auto fn = [](auto const i) noexcept -> T { return {i}; }; + auto const a = fn(true); + static_assert(std::is_same_v); + CHECK(a.has_value()); + CHECK(a.value() == fn::sum{true}); + + auto b = fn(helper{5}); + static_assert(std::is_same_v); + CHECK(b.has_value()); + CHECK(b.value().get_ptr()->v == 5 * helper::from_lval_const); + } + + WHEN("copy ctor") + { + using T = fn::choice; + auto a = T{helper{1}}; + a.value().get_ptr()->v = 23; + auto const b = std::as_const(a); + + CHECK(b.has_value()); + CHECK(b.value().get_ptr()->v == 23 * helper::from_lval_const); + } + + WHEN("move ctor") + { + using T = fn::choice; + auto a = T{helper{1}}; + a.value().get_ptr()->v = 29; + auto const b = std::move(a); + + CHECK(b.has_value()); + CHECK(b.value().get_ptr()->v == 29 * helper::from_rval); + } + } + + WHEN("constructor from sum") + { + using T = fn::choice; + WHEN("move from rvalue") + { + fn::sum h{helper{1}}; + h.get_ptr()->v = 17; + T const a{std::move(h)}; + CHECK(a.value().get_ptr()->v == 17 * helper::from_rval); + } + WHEN("copy from const lvalue") + { + fn::sum h{helper{1}}; + h.get_ptr()->v = 19; + T const a{std::as_const(h)}; + CHECK(a.value().get_ptr()->v == 19 * helper::from_lval_const); } } @@ -443,7 +582,7 @@ TEST_CASE("choice and_then", "[choice][and_then]") TEST_CASE("choice transform", "[choice][transform]") { - WHEN("size 1") + WHEN("size 2, only one set") { using type = fn::choice; constexpr auto init = std::in_place_type; @@ -503,15 +642,14 @@ TEST_CASE("choice transform", "[choice][transform]") == fn::choice{5.25}); } - WHEN("size 4") + WHEN("size 2") { - static constexpr auto sizeof_string = sizeof(std::string); using ::fn::choice; using ::fn::sum; constexpr auto fn1 = [](auto i) noexcept -> std::size_t { return sizeof(i); }; - using type = choice; - static_assert(type::size == 4); + using type = choice; + static_assert(type::size == 2); WHEN("element v0 set") { @@ -583,84 +721,5 @@ TEST_CASE("choice transform", "[choice][transform]") == choice{true}); } } - - WHEN("element v2 set") - { - type a{std::in_place_type, "bar"}; - CHECK(a.data.v2 == "bar"); - WHEN("value only") - { - // TODO Change single CHECK below to static_assert when supported by Clang - CHECK(type{std::in_place_type, "bar"}.transform(fn1) == choice{sizeof_string}); - CHECK(a.transform( // - fn::overload( // - [](auto) -> sum { throw 1; }, - [](std::string &i) -> bool { return i == "bar"; }, [](std::string const &) -> bool { throw 0; }, - [](std::string &&) -> bool { throw 0; }, [](std::string const &&) -> bool { throw 0; })) - == choice{true}); - CHECK(std::as_const(a).transform( // - fn::overload( // - [](auto) -> sum { throw 1; }, [](std::string &) -> bool { throw 0; }, - [](std::string const &i) -> bool { return i == "bar"; }, [](std::string &&) -> bool { throw 0; }, - [](std::string const &&) -> bool { throw 0; })) - == choice{true}); - CHECK(type{std::in_place_type, "bar"}.transform( // - fn::overload( // - [](auto) -> sum { throw 1; }, [](std::string &) -> bool { throw 0; }, - [](std::string const &) -> bool { throw 0; }, [](std::string &&i) -> bool { return i == "bar"; }, - [](std::string const &&) -> bool { throw 0; })) - == choice{true}); - CHECK(std::move(std::as_const(a)) - .transform( // - fn::overload( // - [](auto) -> sum { throw 1; }, [](std::string &) -> bool { throw 0; }, - [](std::string const &) -> bool { throw 0; }, [](std::string &&) -> bool { throw 0; }, - [](std::string const &&i) -> bool { return i == "bar"; })) - == choice{true}); - } - } - - WHEN("element v3 set") - { - type a{std::in_place_type, "baz"}; - CHECK(a.data.v3 == "baz"); - WHEN("value only") - { - static_assert(type{std::in_place_type, "baz"}.transform(fn1) == choice{16uz}); - CHECK(a.transform( // - fn::overload( // - [](auto) -> sum { throw 1; }, - [](std::string_view &i) -> sum { return {i == "baz"}; }, - [](std::string_view const &) -> sum { throw 0; }, - [](std::string_view &&) -> sum { throw 0; }, - [](std::string_view const &&) -> sum { throw 0; })) - == choice{true}); - CHECK(std::as_const(a).transform( // - fn::overload( // - [](auto) -> sum { throw 1; }, - [](std::string_view &) -> sum { throw 0; }, - [](std::string_view const &i) -> sum { return {i == "baz"}; }, - [](std::string_view &&) -> sum { throw 0; }, - [](std::string_view const &&) -> sum { throw 0; })) - == choice{true}); - CHECK(type{std::in_place_type, "baz"}.transform( // - fn::overload( // - [](auto) -> sum { throw 1; }, - [](std::string_view &) -> sum { throw 0; }, - [](std::string_view const &) -> sum { throw 0; }, - [](std::string_view &&i) -> sum { return {i == "baz"}; }, - [](std::string_view const &&) -> sum { throw 0; })) - == choice{true}); - CHECK(std::move(std::as_const(a)) - .transform( // - fn::overload( // - [](auto) -> sum { throw 1; }, - [](std::string_view &) -> sum { throw 0; }, - [](std::string_view const &) -> sum { throw 0; }, - [](std::string_view &&) -> sum { throw 0; }, - [](std::string_view const &&i) -> sum { return {i == "baz"}; })) - == choice{true}); - } - } } } diff --git a/tests/pfn/expected.cpp b/tests/pfn/expected.cpp new file mode 100644 index 00000000..65e98a4a --- /dev/null +++ b/tests/pfn/expected.cpp @@ -0,0 +1,4184 @@ +// Copyright (c) 2025 Bronek Kozicki +// +// Distributed under the ISC License. See accompanying file LICENSE.md +// or copy at https://opensource.org/licenses/ISC + +#ifndef PFN_TEST_VALIDATION +// TODO: Add death tests. Until then, empty definition to avoid false "no coverage" reports +#define LIBFN_ASSERT(...) +#include +using pfn::bad_expected_access; +using pfn::expected; +using pfn::unexpect; +using pfn::unexpect_t; +using pfn::unexpected; +#else +#include +using std::bad_expected_access; +using std::expected; +using std::unexpect; +using std::unexpect_t; +using std::unexpected; +#endif + +#include + +#include + +#include +#include +#include +#include +#include + +enum Error { unknown = 1, file_not_found = 5 }; + +TEST_CASE("bad_expected_access", "[expected][polyfill][bad_expected_access]") +{ + std::string const e1 = "bad access to expected"; + std::string const e2 = "bad access to std::expected"; + + SECTION("bad_expected_access") + { + struct T : bad_expected_access {}; + + static_assert(noexcept(T{})); + T a; + static_assert(noexcept(T{a})); + static_assert(noexcept(T{std::move(a)})); + static_assert(noexcept(a.what())); + static_assert(std::is_same_v); + SECTION("constructors and assignment") + { + T a1 = [&]() -> T & { return a; }(); + CHECK(a.what() == a1.what()); + T a2 = [&]() -> T && { return std::move(a); }(); + CHECK(a.what() == a2.what()); + T a3 = [&]() -> T const & { return a; }(); + CHECK(a.what() == a3.what()); + T a4 = [&]() -> T const && { return std::move(a); }(); + CHECK(a.what() == a4.what()); + + a = [&]() -> T & { return a; }(); + CHECK(T{}.what() == a.what()); + a = [&]() -> T && { return std::move(a); }(); + CHECK(T{}.what() == a.what()); + a = [&]() -> T const & { return a; }(); + CHECK(T{}.what() == a.what()); + a = [&]() -> T const && { return std::move(a); }(); + CHECK(T{}.what() == a.what()); + } + std::string const tmp = a.what(); + CHECK(((tmp.substr(0, e1.size()) == e1) || (tmp.substr(0, e2.size()) == e2))); + + T const b; + CHECK(&decltype(a)::what == &decltype(b)::what); + CHECK(a.what() == b.what()); + + static_assert(noexcept(T{b})); + static_assert(noexcept(T{std::move(b)})); + static_assert(noexcept(b.what())); + static_assert(std::is_same_v); + } + + SECTION("bad_expected_access") + { + using T = bad_expected_access; + static_assert(std::is_base_of_v, T>); + + SECTION("type and noexcept") + { + T a{12}; + static_assert(noexcept(T{a})); + static_assert(noexcept(T{std::move(a)})); + static_assert(noexcept(a.what())); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + SECTION("copy/move constructors") + { + T b{1}; + + SECTION("lval") + { + b.error().v = 11; + T c = b; + CHECK(c.error().v == 11 * helper::from_lval_const); + } + + SECTION("lval const") + { + b.error().v = 13; + T c = std::as_const(b); + CHECK(c.error().v == 13 * helper::from_lval_const); + } + + SECTION("rval") + { + b.error().v = 17; + T c = std::move(b); + CHECK(c.error().v == 17 * helper::from_rval); + } + + SECTION("rval cont") + { + b.error().v = 19; + T c = std::move(std::as_const(b)); + CHECK(c.error().v == 19 * helper::from_lval_const); + } + } + + SECTION("assignment") + { + T a{12}; + T b{1}; + + SECTION("lval") + { + b.error().v = 11; + a = b; + CHECK(a.error().v == 11 * helper::from_lval_const); + } + + SECTION("lval const") + { + b.error().v = 13; + a = std::as_const(b); + CHECK(a.error().v == 13 * helper::from_lval_const); + } + + SECTION("rval") + { + b.error().v = 17; + a = std::move(b); + CHECK(a.error().v == 17 * helper::from_rval); + } + + SECTION("rval const") + { + b.error().v = 19; + a = std::move(std::as_const(b)); + CHECK(a.error().v == 19 * helper::from_lval_const); + } + } + + SECTION("accessors") + { + helper c{1}; + T b{1}; + + SECTION("lval") + { + b.error().v = 11; + c = b.error(); + CHECK(c.v == 11 * helper::from_lval); + } + + SECTION("lval const") + { + b.error().v = 13; + c = std::as_const(b).error(); + CHECK(c.v == 13 * helper::from_lval_const); + } + + SECTION("rval") + { + b.error().v = 17; + c = std::move(b).error(); + CHECK(c.v == 17 * helper::from_rval); + } + + SECTION("rval const") + { + b.error().v = 19; + c = std::move(std::as_const(b)).error(); + CHECK(c.v == 19 * helper::from_rval_const); + } + } + + SECTION("bad_expected_access") + { + T a{12}; + std::string const tmp = a.what(); + CHECK(((tmp.substr(0, e1.size()) == e1) || (tmp.substr(0, e2.size()) == e2))); + auto const c = []() { + struct C : bad_expected_access {}; + return C{}; + }(); + CHECK(a.what() == c.what()); + } + } +} + +template struct dummy final { + decltype(V) value = V; +}; + +TEST_CASE("unexpect", "[expected][polyfill][unexpect]") +{ + static_assert(std::is_empty_v); + static_assert(noexcept(unexpect_t{})); + static_assert(std::is_same_v); + + // unexpect can be used as a NTTP + static_assert(not std::is_empty_v>); + static constexpr auto a = unexpect; + static_assert(not std::is_empty_v>); + static_assert(std::is_same_v); + static_assert(std::is_same_v, dummy>); + + SUCCEED(); +} + +TEST_CASE("unexpected", "[expected][polyfill][unexpected]") +{ +#ifndef PFN_TEST_VALIDATION + SECTION("is_valid_unexpected") + { + using pfn::detail::_is_valid_unexpected; + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected); + static_assert(not _is_valid_unexpected<::pfn::unexpected>); + static_assert(not _is_valid_unexpected<::pfn::unexpected>); + static_assert(_is_valid_unexpected); + static_assert(_is_valid_unexpected); + static_assert(_is_valid_unexpected>); + SUCCEED(); + } +#endif + + SECTION("constructors") + { + SECTION("CTAD") + { + constexpr unexpected c{Error::file_not_found}; + static_assert(c.error() == Error::file_not_found); + static_assert(std::is_same_v const>); + static_assert(std::is_nothrow_constructible_v); + SUCCEED(); + + { + unexpected c{helper{2}}; + CHECK(c.error().v == 2 * helper::from_rval); + CHECK(c != unexpected{helper(3)}); + static_assert(std::is_same_v>); + static_assert(std::is_nothrow_constructible_v); + } + } + + SECTION("no CTAD") + { + constexpr unexpected c{42}; + static_assert(c.error() == 42); + static_assert(std::is_nothrow_constructible_v); + SUCCEED(); + + { + unexpected c(3); + CHECK(c.error().v == 3); + CHECK(c == unexpected(std::in_place, 3)); + static_assert(not std::is_nothrow_constructible_v); + } + } + + SECTION("in-place, no CTAD") + { + unexpected c(std::in_place, 3, 5); + CHECK(c.error().v == 3 * 5); + static_assert(not std::is_nothrow_constructible_v); + + c.error().v *= helper::from_rval; + CHECK(c == unexpected(std::in_place, helper{15})); + } + + SECTION("in_place, not CTAD, initializer_list, noexcept(false)") + { + SECTION("forwarded args") + { + unexpected c(std::in_place, {3.0, 5.0}); + auto const d = 3 * 5; + CHECK(c.error().v == d); + static_assert(std::is_nothrow_constructible_v); + } + + SECTION("no forwarded args") + { + unexpected c(std::in_place, {2.0, 2.5}); + CHECK(c.error().v == 5); + static_assert(std::is_nothrow_constructible_v); + } + + SECTION("exception thrown") + { + unexpected t{13}; + try { + t = unexpected{std::in_place, 1, 2, 0}; + FAIL(); + } catch (std::runtime_error const &) { + SUCCEED(); + } + CHECK(t.error().v == 13); + } + } + } + + SECTION("accessors") + { + helper a{1}; + + SECTION("lval") + { + unexpected t{13}; + a = t.error(); + CHECK(a.v == 13 * helper::from_lval); + } + + SECTION("lval const") + { + unexpected const t{17}; + a = t.error(); + CHECK(a.v == 17 * helper::from_lval_const); + } + + SECTION("rval") + { + unexpected t{19}; + a = std::move(t).error(); + CHECK(a.v == 19 * helper::from_rval); + } + + SECTION("rval const") + { + unexpected const t{23}; + a = std::move(t).error(); + CHECK(a.v == 23 * helper::from_rval_const); + } + } + + SECTION("assignment") + { + unexpected a{1}; + + SECTION("lval") + { + unexpected t{13}; + a = t; + CHECK(a.error().v == 13 * helper::from_lval_const); + } + + SECTION("lval const") + { + unexpected const t{17}; + a = t; + CHECK(a.error().v == 17 * helper::from_lval_const); + } + + SECTION("rval") + { + unexpected t{19}; + a = std::move(t); + CHECK(a.error().v == 19 * helper::from_rval); + } + + SECTION("rval const") + { + unexpected const t{23}; + a = std::move(t); + CHECK(a.error().v == 23 * helper::from_lval_const); + } + } + + SECTION("swap") + { + unexpected a{1}; + a.error().v = 2; + unexpected b{helper{1}}; + b.error().v = 3; + a.swap(b); + CHECK(a.error().v == 3 * helper::swapped); + CHECK(b.error().v == 2 * helper::swapped); + b.error() = helper{11}; + swap(a, b); + CHECK(a.error().v == 11 * helper::from_rval * helper::swapped); + CHECK(b.error().v == 3 * helper::swapped * helper::swapped); + } + + SECTION("constexpr") + { + constexpr auto fn = [](auto i) constexpr { + unexpected a{i}; + unexpected b{i * 5}; + swap(a, b); + unexpected c{1}; + c = b; + b.swap(c); + return unexpected{b.error() * a.error() * 7}; + }; + + constexpr auto c = fn(21); + static_assert(std::is_same_v const>); + static_assert(c.error() == 21 * 21 * 5 * 7); + + SUCCEED(); + } +} + +namespace { + +template struct non_swappable { + friend void swap(T &, T &) = delete; +}; + +template struct swappable { + friend void swap(T &, T &) noexcept(false) {} +}; + +template struct nothrow_swappable { + friend void swap(T &, T &) noexcept(true) {} +}; + +// NOTE: not the same as std::is_swappable https://eel.is/c++draft/swappable.requirements +// because std::is_swappable brings std::swap https://eel.is/c++draft/utility.swap#lib:swap +// into scope, which we do not want for this check. +template +concept is_swappable = requires { swap(std::declval(), std::declval()); }; + +} // namespace + +TEST_CASE("expected non void", "[expected][polyfill]") +{ +#ifndef PFN_TEST_VALIDATION + constexpr bool extension = true; +#else + constexpr bool extension = false; +#endif + + SECTION("constructors") + { + SECTION("default unavailable") + { + static_assert(not std::is_default_constructible_v); // prerequisite + static_assert(not std::is_default_constructible_v>); + static_assert(std::is_default_constructible_v>); + SUCCEED(); + } + + SECTION("default trivial") + { + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + static_assert(not extension || std::is_trivially_destructible_v); + + constexpr T a; + static_assert(a.has_value()); + static_assert(a.value() == 0); + SUCCEED(); + } + + struct A { + constexpr A() noexcept(true) {} + constexpr bool operator==(A const &) const = default; + + private: + int v = 12; + }; + + SECTION("default noexcept(true) from value type") + { + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + static_assert(not extension || std::is_trivially_destructible_v); + + constexpr T a; + static_assert(a.has_value()); + static_assert(a.value() == A()); + + T b; + CHECK(b.has_value()); + CHECK(b.value() == A()); + } + + struct B { + constexpr B() noexcept(false) {} + int v = 42; + }; + + SECTION("default noexcept(false) from value type") + { + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not std::is_nothrow_constructible_v); + + constexpr T a; + static_assert(a.has_value()); + static_assert(a.value().v == 42); + + T b; + CHECK(b.has_value()); + CHECK(b.value().v == 42); + } + + SECTION("default ignore noexcept from error type") + { + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + SUCCEED(); + } + + struct C { + C() noexcept(false) { throw 7; } + }; + + SECTION("default exception thrown") + { + try { + expected b; + FAIL(); + } catch (int i) { + CHECK(i == 7); + } + + // not a problem if error type ctor is throwing + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + + T b; + CHECK(b.has_value()); + CHECK(b.value() == 0); + } + + SECTION("value from other expected rval") + { + using T = expected; + static_assert(std::is_constructible_v>); + static_assert(std::is_constructible_v>); + static_assert(not std::is_nothrow_constructible_v>); + static_assert(not extension || std::is_nothrow_constructible_v>); + + constexpr T b(expected(unexpect, Error::unknown)); + static_assert(b.error() == Error::unknown); + + T c(expected(3)); + CHECK(c.value().v == 3); + } + + SECTION("error from other expected rval") + { + using T = expected; + static_assert(std::is_constructible_v>); + + T d(expected(unexpect, 2)); + CHECK(d.error().v == 2 * helper::from_rval); + } + + SECTION("value from other expected lval const") + { + using T = expected; + static_assert(std::is_constructible_v const &>); + static_assert(not std::is_nothrow_constructible_v const &>); + static_assert(not extension || std::is_nothrow_constructible_v const &>); + + constexpr expected v(5); + constexpr expected e(unexpect, Error::file_not_found); + constexpr T b(e); + static_assert(b.error() == Error::file_not_found); + + T c(v); + CHECK(c.value().v == 5); + T d(e); + CHECK(d.error() == Error::file_not_found); + } + + SECTION("error from other expected lval const") + { + using T = expected; + static_assert(std::is_constructible_v>); + + expected const e(unexpect, 3); + T d(e); + CHECK(d.error().v == 3 * helper::from_lval_const); + } + + SECTION("converting") + { + using T = expected; + static_assert(std::is_constructible_v); + static_assert(not std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + + T const b(11); + CHECK(b.value().v == 11); + + T const c(helper(13)); + CHECK(c.value().v == 13 * helper::from_rval); + } + + SECTION("from unexpected rval") + { + using T = expected; + static_assert(std::is_constructible_v>); + static_assert(not std::is_nothrow_constructible_v>); + static_assert(std::is_constructible_v>); + static_assert(not extension || std::is_nothrow_constructible_v>); + + constexpr expected a(unexpected(true)); + static_assert(a.error() == 1); + + T const b(unexpected(5)); + CHECK(b.error().v == 5); + } + + SECTION("from unexpected lval const") + { + using T = expected; + constexpr auto g1 = unexpected(5); + constexpr expected a(g1); + static_assert(a.error() == 5); + + T const b(g1); + CHECK(b.error().v == 5); + } + + SECTION("with in_place") + { + using T = expected; + static_assert(std::is_constructible_v); + static_assert(not std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + + T const b(std::in_place, 11, 13); + CHECK(b.value().v == 11 * 13); + + T const c(std::in_place, {2.0, 3.0, 5.0}); + CHECK(c.value().v == 2 * 3 * 5.0); + + try { + T const d(std::in_place, 1, 2, 0); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + } + } + + SECTION("with unexpect") + { + using T = expected; + static_assert(std::is_constructible_v); + static_assert(not std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + + T const b(unexpect, 11, 13); + CHECK(b.error().v == 11 * 13); + + T const c(unexpect, {2.0, 3.0, 5.0}); + CHECK(c.error().v == 2 * 3 * 5.0); + + try { + T const d(unexpect, 1, 2, 0); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + } + } + } + + SECTION("copy, move and dtor") + { + struct U { + U() = default; + U(U const &) = delete; + }; + + SECTION("unavailable") + { + static_assert(not std::is_copy_constructible_v); // prerequisite + static_assert(not std::is_trivially_copy_constructible_v); // prerequisite + static_assert(not std::is_copy_constructible_v>); + static_assert(not std::is_copy_constructible_v>); + static_assert(not std::is_move_constructible_v>); + static_assert(not std::is_move_constructible_v>); + SUCCEED(); + } + + SECTION("trivial") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(std::is_trivially_copy_constructible_v); + static_assert(not extension || std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(not extension || std::is_nothrow_move_constructible_v); + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + constexpr T a; + constexpr T b = a; + static_assert(b.has_value() && a.value() == b.value()); + + { + T a(std::in_place, 13); + T b = a; + CHECK(b.has_value()); + CHECK(b.value() == 13); + + T c = std::move(a); + CHECK(c.has_value()); + CHECK(c.value() == 13); + } + } + + SECTION("non-trivial value type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not extension || std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); // required + static_assert(not std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + { + T a(std::in_place, 13); + T b = a; // no overload for lval + CHECK(b.has_value()); + CHECK(b.value().v == 13 * helper::from_lval_const); + + T c = std::as_const(a); + CHECK(b.has_value()); + CHECK(c.value().v == 13 * helper::from_lval_const); + + T d = std::move(std::as_const(a)); // no overload for lval const + CHECK(b.has_value()); + CHECK(d.value().v == 13 * helper::from_lval_const); + + T e = std::move(a); + CHECK(b.has_value()); + CHECK(e.value().v == 13 * helper::from_rval); + } + } + + SECTION("non-trivial error type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not extension || std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); // required + static_assert(not std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + { + T a(unexpect, 33); + T b = a; // no overload for lval + CHECK(not b.has_value()); + CHECK(b.error().v == 33 * helper::from_lval_const); + + T c = std::as_const(a); + CHECK(not b.has_value()); + CHECK(c.error().v == 33 * helper::from_lval_const); + + T d = std::move(std::as_const(a)); // no overload for lval const + CHECK(not b.has_value()); + CHECK(d.error().v == 33 * helper::from_lval_const); + + T e = std::move(a); + CHECK(not b.has_value()); + CHECK(e.error().v == 33 * helper::from_rval); + } + } + + SECTION("non-trivial both") + { + using T = expected>; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not extension || std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); // required + static_assert(not std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + { + T a(std::in_place, 41); + T b = a; // no overload for lval + CHECK(b.has_value()); + CHECK(b.value().v == 41 * helper::from_lval_const); + } + + { + T a(unexpect, 43); + T b = a; // no overload for lval + CHECK(not b.has_value()); + CHECK(b.error().v == 43 * helper::from_lval_const); + } + } + + struct B { + int v; + constexpr B(int v) : v(v) {} + constexpr B(B const &s) noexcept(false) : v(s.v) {}; + constexpr B(B &&s) noexcept(false) : v(s.v) {}; + }; + + SECTION("noexcept(false) from value type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); // required + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + constexpr T a(std::in_place, 17); + constexpr T b = a; + static_assert(b.has_value() && a.value().v == b.value().v); + + { + T const a(std::in_place, 19); + T b = a; + CHECK(b.has_value()); + CHECK(b.value().v == 19); + + T c = std::move(a); + CHECK(b.has_value()); + CHECK(c.value().v == 19); + } + } + + SECTION("noexcept(false) from error type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); // required + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + constexpr T a(unexpect, 23); + constexpr T b = a; + static_assert(not b.has_value() && a.error().v == b.error().v); + + { + T const a(unexpect, 29); + T b = a; + CHECK(not b.has_value()); + CHECK(b.error().v == 29); + + T c = std::move(a); + CHECK(not c.has_value()); + CHECK(c.error().v == 29); + } + } + + struct C { + constexpr C() noexcept(true) {}; + constexpr C(C const &) noexcept(true) {}; // WORKAROUND:MSVC + constexpr ~C() noexcept(false) {}; + }; + + SECTION("noexcept(false) dtor value type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); // required + static_assert(not std::is_trivially_destructible_v); + + constexpr T a(std::in_place); + constexpr T b = a; + static_assert(b.has_value()); + + { + T const a(std::in_place); + T b = a; + CHECK(b.has_value()); + } + } + + SECTION("noexcept(false) dtor error type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); // required + static_assert(not std::is_trivially_destructible_v); + + constexpr T a(unexpect); + constexpr T b = a; + static_assert(not b.has_value()); + + { + T const a(unexpect); + T b = a; + CHECK(not b.has_value()); + } + } + } + + SECTION("assignment") + { + using M = helper_t<2>; // nothrow move constructible + using E = helper_t<3>; // may throw on move and copy + using C = helper_t<4>; // nothrow copy constructible + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + SECTION("from rval") + { + SECTION("value to value") + { + using T = expected; + + { + static_assert(std::is_nothrow_assignable_v); // required + + T a(std::in_place, 3); + a = T(std::in_place, 5); + CHECK(a.value().v == 5 * helper::from_rval); + } + + { + static_assert(not extension || std::is_nothrow_assignable_v); + + T a(std::in_place, 3); + a = helper(5); + CHECK(a.value().v == 5 * helper::from_rval); + } + } + + SECTION("value to error") + { + SECTION("nothrow move") + { + using T = expected; + static_assert(std::is_nothrow_assignable_v); // required + + { + T a(std::in_place, 3); + a = T(unexpect, 5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place, 4); + try { + a = T(unexpect, {0.0}); + SUCCEED(); + } catch (std::runtime_error const &) { + FAIL(); + } + } + + { + static_assert(not extension || std::is_nothrow_assignable_v &&>); + T a(std::in_place, 4); + a = unexpected(5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place, 4); + try { + a = unexpected({0.0}); + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + } + + SECTION("throwing") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); // required + + { + T a(std::in_place, 3); + a = T(unexpect, 5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place, 4); + try { + a = T(unexpect, {0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 4); + } + } + + { + static_assert(not std::is_nothrow_assignable_v &&>); + T a(std::in_place, 4); + a = unexpected(5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place, 4); + try { + a = unexpected({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 4); + } + } + } + + SECTION("nothrow copy") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); // required + + { + T a(std::in_place, 3); + a = T(unexpect, 5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place, 4); + try { + a = T(unexpect, {0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 4); + } + } + + { + static_assert(not std::is_nothrow_assignable_v &&>); + T a(std::in_place, 4); + a = unexpected(5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place, 4); + try { + a = unexpected({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 4); + } + } + } + } + + SECTION("error to value") + { + SECTION("nothrow move") + { + using T = expected; + static_assert(std::is_nothrow_assignable_v); // required + + { + T a(unexpect, Error::file_not_found); + a = T(std::in_place, 5); + CHECK(a.value().v == 5 * helper::from_rval); + } + + { + T a(unexpect, Error::file_not_found); + try { + a = T(std::in_place, {0.0}); + // expected must not use throwing copy constructor + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + + { + static_assert(not extension || std::is_nothrow_assignable_v); + T a(unexpect, Error::file_not_found); + a = M(5); + CHECK(a.value().v == 5 * helper::from_rval); + } + + { + T a(unexpect, Error::file_not_found); + try { + a = M({0.0}); + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + } + + SECTION("throwing") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); // required + + { + T a(unexpect, Error::file_not_found); + a = T(std::in_place, 5); + CHECK(a.value().v == 5 * helper::from_rval); + } + + { + T a(unexpect, Error::file_not_found); + try { + a = T(std::in_place, {0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + + { + static_assert(not std::is_nothrow_assignable_v); + T a(unexpect, Error::file_not_found); + a = E(5); + CHECK(a.value().v == 5 * helper::from_rval); + } + + { + T a(unexpect, Error::file_not_found); + try { + a = E({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + } + + SECTION("nothrow copy") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); // required + + { + T a(unexpect, Error::file_not_found); + a = T(std::in_place, 5); + CHECK(a.value().v == 5 * helper::from_rval); + } + + { + T a(unexpect, Error::file_not_found); + try { + a = T(std::in_place, {0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + + { + static_assert(not std::is_nothrow_assignable_v); + T a(unexpect, Error::file_not_found); + a = C(5); + CHECK(a.value().v == 5 * helper::from_rval); + } + + { + T a(unexpect, Error::file_not_found); + try { + a = C({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + } + } + + SECTION("error to error") + { + using T = expected; + static_assert(std::is_nothrow_assignable_v); // required + + T a(unexpect, 3); + a = T(unexpect, 5); + CHECK(a.error().v == 5 * helper::from_rval); + + a = unexpected(7); + CHECK(a.error().v == 7 * helper::from_rval); + } + + SECTION("constexpr") + { + using T = expected; + + SECTION("from error") + { + constexpr auto fn = [](T &&v) constexpr -> T { + T tmp{unexpect, Error::unknown}; + tmp = std::move(v); + return tmp; + }; + + constexpr T a = fn(T(unexpect, Error::file_not_found)); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(T(std::in_place, helper::list_t(), 7)); + static_assert(b.value().v == 7 * helper::from_rval * helper::from_rval); + + SUCCEED(); + } + + SECTION("from value") + { + { + constexpr auto fn = [](T &&v) constexpr -> T { + T tmp{std::in_place, helper::list_t(), 13}; + tmp = std::move(v); + return tmp; + }; + + constexpr T a = fn(T(unexpect, Error::file_not_found)); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(T(std::in_place, helper::list_t(), 11)); + static_assert(b.value().v == 11 * helper::from_rval * helper::from_rval); + + SUCCEED(); + } + } + } + } + + SECTION("from lval const") + { + SECTION("value to value") + { + using T = expected; + { + static_assert(not extension || std::is_nothrow_assignable_v); + + T a(std::in_place, 3); + T const b(std::in_place, 5); + a = b; + CHECK(a.value().v == 5 * helper::from_lval_const); + + T c(std::in_place, 7); + a = c; + CHECK(a.value().v == 7 * helper::from_lval_const); + + T const d(std::in_place, 11); + a = std::move(d); + CHECK(a.value().v == 11 * helper::from_lval_const); + } + + { + static_assert(not extension || std::is_nothrow_assignable_v); + + T a(std::in_place, 3); + helper const b(5); + a = b; + CHECK(a.value().v == 5 * helper::from_lval_const); + + helper c(7); + a = c; + CHECK(a.value().v == 7 * helper::from_lval); + + helper const d(11); + a = std::move(d); + CHECK(a.value().v == 11 * helper::from_rval_const); + } + } + + SECTION("value to error") + { + SECTION("nothrow move") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + + { + T a(std::in_place, 3); + T const b(unexpect, 5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const * helper::from_rval); + } + + { + T a(std::in_place, 4); + try { + T const b(unexpect, {0.0}); + a = b; // copy construction on a side of `New tmp` will throw + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 4); + } + } + + { + static_assert(not std::is_nothrow_assignable_v const &>); + T a(std::in_place, 4); + unexpected const b(5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const * helper::from_rval); + } + + { + T a(std::in_place, 4); + try { + unexpected const b({0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 4); + } + } + } + + SECTION("throwing") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + + { + T a(std::in_place, 3); + T const b(unexpect, 5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place, 4); + try { + T const b(unexpect, {0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 4); + } + } + + { + static_assert(not std::is_nothrow_assignable_v const &>); + T a(std::in_place, 4); + unexpected const b(5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place, 4); + try { + unexpected const b({0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 4); + } + } + } + + SECTION("nothrow copy") + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + + { + T a(std::in_place, 3); + T const b(unexpect, 5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place, 4); + try { + T const b(unexpect, {0.0}); + a = b; + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + + { + static_assert(not extension || std::is_nothrow_assignable_v const &>); + T a(std::in_place, 4); + unexpected const b(5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place, 4); + try { + unexpected b(std::in_place, {0.0}); + a = b; + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + } + } + + SECTION("error to value") + { + SECTION("nothrow move") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + + { + T a(unexpect, Error::file_not_found); + T const b(std::in_place, 5); + a = b; + CHECK(a.value().v == 5 * helper::from_lval_const * helper::from_rval); + } + + { + T a(unexpect, Error::file_not_found); + try { + T const b(std::in_place, {0.0}); + a = b; // copy construction on a side of `New tmp` will throw + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + + { + static_assert(not std::is_nothrow_assignable_v); + T a(unexpect, Error::file_not_found); + M const b(5); + a = b; + CHECK(a.value().v == 5 * helper::from_lval_const * helper::from_rval); + } + + { + T a(unexpect, Error::file_not_found); + try { + M const b({0.0}); + a = b; // copy construction on a side of `New tmp` will throw + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + } + + SECTION("throwing") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + + { + T a(unexpect, Error::file_not_found); + T const b(std::in_place, 5); + a = b; + CHECK(a.value().v == 5 * helper::from_lval_const); + } + + { + T a(unexpect, Error::file_not_found); + try { + T const b(std::in_place, {0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + + { + static_assert(not std::is_nothrow_assignable_v); + T a(unexpect, Error::file_not_found); + E const b(5); + a = b; + CHECK(a.value().v == 5 * helper::from_lval_const); + } + + { + T a(unexpect, Error::file_not_found); + try { + E const b({0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + } + + SECTION("nothrow copy") + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + + { + T a(unexpect, Error::file_not_found); + T const b(std::in_place, 5); + a = b; + CHECK(a.value().v == 5 * helper::from_lval_const); + } + + { + T a(unexpect, Error::file_not_found); + try { + T const b(std::in_place, {0.0}); + a = b; + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + + { + static_assert(not extension || std::is_nothrow_assignable_v); + T a(unexpect, Error::file_not_found); + C const b(5); + a = b; + CHECK(a.value().v == 5 * helper::from_lval_const); + } + + { + T a(unexpect, Error::file_not_found); + try { + C const b({0.0}); + a = b; + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + } + } + + SECTION("error to error") + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + + T a(unexpect, 3); + T const b(unexpect, 5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + + unexpected const c(7); + a = c; + CHECK(a.error().v == 7 * helper::from_lval_const); + } + } + + SECTION("constexpr") + { + using T = expected; + constexpr T c{unexpect, Error::file_not_found}; + constexpr T d{std::in_place, helper::list_t(), 5}; + + SECTION("from error") + { + constexpr auto fn = [](T const &v) constexpr -> T { + T tmp{unexpect, Error::unknown}; + tmp = v; + return tmp; + }; + + constexpr T a = fn(c); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(d); + static_assert(b.value().v == 5 * helper::from_lval_const * helper::from_rval); + + SUCCEED(); + } + + SECTION("from value") + { + { + constexpr auto fn = [](T const &v) constexpr -> T { + T tmp{std::in_place, helper::list_t(), 13}; + tmp = v; + return tmp; + }; + + constexpr T a = fn(c); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(d); + static_assert(b.value().v == 5 * helper::from_lval_const * helper::from_rval); + + SUCCEED(); + } + } + } + } + + SECTION("emplace") + { + using T = expected, Error>; + SECTION("value to value") + { + { + T a(std::in_place, 13); + a.emplace(2, 3, 5); + CHECK(a.value().v == 2 * 3 * 5); + } + + { + T a(std::in_place, 13); + a.emplace({7.0, 11.0}); + CHECK(a.value().v == 7 * 11); + } + } + + SECTION("error to value") + { + { + T a(unexpect, Error::file_not_found); + a.emplace(2, 3, 5); + CHECK(a.value().v == 2 * 3 * 5); + } + + { + T a(unexpect, Error::file_not_found); + a.emplace({7.0, 11.0}); + CHECK(a.value().v == 7 * 11); + } + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 5}; + + SECTION("from error") + { + constexpr auto fn = [](auto &&...args) constexpr -> T { + T tmp{unexpect, Error::unknown}; + tmp.emplace(std::forward(args)...); + return tmp; + }; + + constexpr T a = fn(c); + static_assert(a.value().v == 5 * helper::from_lval_const * helper::from_rval); + + constexpr T b = fn(helper::list_t{3.0, 11.0}, 7); + static_assert(b.value().v == 3 * 11 * 7 * helper::from_rval); + + SUCCEED(); + } + + SECTION("from value") + { + { + constexpr auto fn = [](auto &&...args) constexpr -> T { + T tmp{std::in_place, helper::list_t(), 13}; + tmp.emplace(std::forward(args)...); + return tmp; + }; + + constexpr T a = fn(c); + static_assert(a.value().v == 5 * helper::from_lval_const * helper::from_rval); + + constexpr T b = fn(helper::list_t{3.0, 11.0}, 7); + static_assert(b.value().v == 3 * 11 * 7 * helper::from_rval); + + SUCCEED(); + } + } + + SECTION("throwing constructor") + { + constexpr auto fn = [](auto &&...args) constexpr -> bool { + return requires { std::declval().emplace(std::forward(args)...); }; + }; + + static_assert(not fn(1, 2)); + + SUCCEED(); + } + } + } + + SECTION("swap") + { + SECTION("non-swappable") + { + struct A : non_swappable {}; + static_assert(not std::is_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + static_assert(not extension || not is_swappable>); + static_assert(not extension || not is_swappable>); + + SUCCEED(); + } + + SECTION("non-move-constructible") + { + struct A : swappable { + A(A &&) = delete; + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(not std::is_move_constructible_v); + + static_assert(not is_swappable>); + static_assert(not is_swappable>); + + SUCCEED(); + } + + SECTION("non-nothrow-move-constructible") + { + struct A : swappable { + A(A &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + struct B : swappable { + B(B &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + static_assert(is_swappable>); + static_assert(not is_swappable>); + + SUCCEED(); + } + + SECTION("nothrow-swappable non-nothrow-move-constructible") + { + struct A : nothrow_swappable { + A(A &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + struct B : nothrow_swappable { + B(B &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + static_assert(is_swappable>); + static_assert(not is_swappable>); + + SUCCEED(); + } + + SECTION("swappabla, non-nothrow-move-constructible") + { + struct A : swappable { + A(A &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + struct B : swappable { + B(B &&) noexcept(true) = default; + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + static_assert(is_swappable>); + + { + using T = expected; + static_assert(is_swappable); + static_assert(not noexcept(swap(std::declval(), std::declval()))); + } + { + using T = expected; + static_assert(is_swappable); + static_assert(not noexcept(swap(std::declval(), std::declval()))); + } + + SUCCEED(); + } + + SECTION("nothrow-swappabla, non-nothrow-move-constructible") + { + struct A : nothrow_swappable { + A(A &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + struct B : nothrow_swappable { + B(B &&) noexcept(true) = default; + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + static_assert(is_swappable>); + + { + using T = expected; + static_assert(is_swappable); + static_assert(not noexcept(swap(std::declval(), std::declval()))); + } + { + using T = expected; + static_assert(is_swappable); + static_assert(not noexcept(swap(std::declval(), std::declval()))); + } + + SUCCEED(); + } + + SECTION("nothrow-swappabla, nothrow-move-constructible") + { + struct A : nothrow_swappable { + A(A &&) noexcept(true) = default; + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + struct B : nothrow_swappable { + B(B &&) noexcept(true) = default; + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + static_assert(is_swappable>); + + { + using T = expected; + static_assert(is_swappable); + static_assert(noexcept(swap(std::declval(), std::declval()))); + } + { + using T = expected; + static_assert(is_swappable); + static_assert(noexcept(swap(std::declval(), std::declval()))); + } + + SUCCEED(); + } + + SECTION("swap same") + { + SECTION("value") + { + using T = expected; + T a(7); + T b(13); + swap(a, b); + CHECK(a.value().v == 13 * helper::swapped); + CHECK(b.value().v == 7 * helper::swapped); + } + + SECTION("error") + { + using T = expected; + T a(unexpect, 17); + T b(unexpect, 23); + swap(a, b); + CHECK(a.error().v == 23 * helper::swapped); + CHECK(b.error().v == 17 * helper::swapped); + } + } + + SECTION("swap error/value") + { + using T = expected>; + T a(unexpect, 19); + T b(27); + swap(a, b); + CHECK(a.value().v == 27 * helper::from_rval); + CHECK(b.error().v == 19 * helper::from_rval * helper::from_rval); + } + + SECTION("swap value/error") + { + SECTION("nothrow") + { + using T = expected>; + T a(17); + T b(unexpect, 29); + swap(a, b); + CHECK(a.error().v == 29 * helper::from_rval * helper::from_rval); + CHECK(b.value().v == 17 * helper::from_rval); + } + + static_assert(not std::is_nothrow_move_constructible_v>); + static_assert(std::is_nothrow_move_constructible_v>); + SECTION("nothrow error") + { + using T = expected, helper_t<30>>; + SECTION("happy path") + { + T a(13); + T b(unexpect, 23); + swap(a, b); + CHECK(a.error().v == 23 * helper::from_rval * helper::from_rval); + CHECK(b.value().v == 13 * helper::from_rval); + } + + SECTION("exception") + { + T a(std::in_place, {0.0}); + T b(unexpect, 23); + try { + swap(a, b); + FAIL(); + } catch (std::runtime_error const &) { + CHECK(a.value().v == 0); + CHECK(b.error().v == 23 * helper::from_rval * helper::from_rval); + } + } + } + + SECTION("nothrow value") + { + using T = expected, helper_t<33>>; + SECTION("happy path") + { + T a(7); + T b(unexpect, 11); + swap(a, b); + CHECK(a.error().v == 11 * helper::from_rval); + CHECK(b.value().v == 7 * helper::from_rval * helper::from_rval); + } + + SECTION("exception") + { + T a(std::in_place, 13); + T b(unexpect, {0.0}); + try { + swap(a, b); + FAIL(); + } catch (std::runtime_error const &) { + CHECK(a.value().v == 13 * helper::from_rval * helper::from_rval); + CHECK(b.error().v == 0); + } + } + } + } + + SECTION("constexpr") + { + using T = expected; + + SECTION("to error") + { + constexpr auto fn = [](T &&v) constexpr -> T { + T tmp{unexpect, Error::unknown}; + swap(tmp, v); + return v; + }; + + constexpr T a = fn(T(unexpect, Error::file_not_found)); + static_assert(a.error() == Error::unknown); + + constexpr T b = fn(T(12)); + static_assert(b.error() == Error::unknown); + + SUCCEED(); + } + + SECTION("to value") + { + constexpr auto fn = [](T &&v) constexpr -> T { + T tmp{7}; + swap(tmp, v); + return v; + }; + + constexpr T a = fn(T(unexpect, Error::file_not_found)); + static_assert(a.value() == 7); + + constexpr T b = fn(T(12)); + static_assert(b.value() == 7); + + SUCCEED(); + } + } + } + + SECTION("accessors") + { + SECTION("value") + { + using T = expected; + + { + T a = {11}; + CHECK(a.value().v == 11); + CHECK(std::as_const(a).value().v == 11); + CHECK(std::move(std::as_const(a)).value().v == 11); + CHECK(std::move(a).value().v == 11); + } + + { + T a = {13}; + CHECK(a); + helper b{1}; + CHECK((b = a.value()).v == 13 * helper::from_lval); + CHECK((b = std::as_const(a).value()).v == 13 * helper::from_lval_const); + CHECK((b = std::move(std::as_const(a)).value()).v == 13 * helper::from_rval_const); + CHECK((b = std::move(a).value()).v == 13 * helper::from_rval); + } + + { + T a = {17}; + helper b{1}; + CHECK(a); + CHECK((b = *a).v == 17 * helper::from_lval); + CHECK((b = *std::as_const(a)).v == 17 * helper::from_lval_const); + CHECK((b = *std::move(std::as_const(a))).v == 17 * helper::from_rval_const); + CHECK((b = *std::move(a)).v == 17 * helper::from_rval); + } + + { + T a{unexpect, Error::file_not_found}; + CHECK(!a); + + try { + auto _ = a.value(); + FAIL(); + } catch (bad_expected_access const &e) { + CHECK(e.error() == Error::file_not_found); + } + + try { + auto _ = std::as_const(a).value(); + FAIL(); + } catch (bad_expected_access const &e) { + CHECK(e.error() == Error::file_not_found); + } + + try { + auto _ = std::move(std::as_const(a)).value(); + FAIL(); + } catch (bad_expected_access const &e) { + CHECK(e.error() == Error::file_not_found); + } + + try { + auto _ = std::move(a).value(); + FAIL(); + } catch (bad_expected_access const &e) { + CHECK(e.error() == Error::file_not_found); + } + } + } + + SECTION("error") + { + using T = expected; + + T a{unexpect, 17}; + CHECK(a.error().v == 17); + CHECK(std::as_const(a).error().v == 17); + CHECK(std::move(std::as_const(a)).error().v == 17); + CHECK(std::move(a).error().v == 17); + + { + helper b{1}; + CHECK((b = a.error()).v == 17 * helper::from_lval); + CHECK((b = std::as_const(a).error()).v == 17 * helper::from_lval_const); + CHECK((b = std::move(std::as_const(a)).error()).v == 17 * helper::from_rval_const); + CHECK((b = std::move(a).error()).v == 17 * helper::from_rval); + } + } + + SECTION("value_or") + { + using T = expected; + static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); + static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); + +#ifndef _MSC_VER + SECTION("value") + { + T a(7); + CHECK(a.value_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::as_const(a).value_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::move(std::as_const(a)).value_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::move(a).value_or(0) == helper(7 * helper::from_rval)); + } + + SECTION("error") + { + { + T a(unexpect, Error::file_not_found); + CHECK(a.value_or(13) == helper(13)); + CHECK(std::move(a).value_or(5) == helper(5)); + } + + { + T const a(unexpect, Error::unknown); + helper b(11); + CHECK(a.value_or(b) == helper(11 * helper::from_lval)); + CHECK(a.value_or(std::as_const(b)) == helper(11 * helper::from_lval_const)); + CHECK(a.value_or(std::move(std::as_const(b))) == helper(11 * helper::from_rval_const)); + CHECK(a.value_or(std::move(b)) == helper(11 * helper::from_rval)); + } + } +#endif + + SECTION("noexcept extension") + { + { + using T = expected, Error>; + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); + + static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); + static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not noexcept(std::declval().value_or(std::declval()))); + } + + { + using T = expected, Error>; + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); + + static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); + } + + SUCCEED(); + } + +#ifndef _MSC_VER + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + + SECTION("lval const") + { + { + constexpr T a(std::in_place, {3.0}, 5); + static_assert(a.value_or(c).v == 3 * 5 * helper::from_lval_const); + } + { + constexpr T a(unexpect, Error::unknown); + static_assert(a.value_or(c).v == 7 * helper::from_lval_const); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{std::in_place, {3.0}, 5}.value_or(c).v == 3 * 5 * helper::from_rval); + static_assert(T{unexpect, Error::unknown}.value_or(c).v == 7 * helper::from_lval_const); + static_assert(T{unexpect, Error::unknown}.value_or(helper(helper::list_t{7.0}, 3)).v + == 7 * 3 * helper::from_rval); + + SUCCEED(); + } + } +#endif + } + + SECTION("error_or") + { + using T = expected; + SECTION("noexcept extension") + { + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + } + + SECTION("error") + { + T a(unexpect, 7); + CHECK(a.error_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::as_const(a).error_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::move(std::as_const(a)).error_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::move(a).error_or(0) == helper(7 * helper::from_rval)); + } + + SECTION("value") + { + { + T a(17); + CHECK(a.error_or(17) == helper(17)); + CHECK(std::move(a).error_or(5) == helper(5)); + } + + { + T const a(23); + helper b(11); + CHECK(a.error_or(b) == helper(11 * helper::from_lval)); + CHECK(a.error_or(std::as_const(b)) == helper(11 * helper::from_lval_const)); + CHECK(a.error_or(std::move(std::as_const(b))) == helper(11 * helper::from_rval_const)); + CHECK(a.error_or(std::move(b)) == helper(11 * helper::from_rval)); + } + } + + SECTION("noexcept extension") + { + { + using T = expected>; + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); + + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + } + + { + using T = expected>; + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); + + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + } + + SUCCEED(); + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + + SECTION("lval const") + { + { + constexpr T a(unexpect, {3.0}, 5); + static_assert(a.error_or(c).v == 3 * 5 * helper::from_lval_const); + } + + { + constexpr T a(std::in_place, 13); + static_assert(a.error_or(c).v == 7 * helper::from_lval_const); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{unexpect, {3.0}, 5}.error_or(c).v == 3 * 5 * helper::from_rval); + static_assert(T{std::in_place, 13}.error_or(c).v == 7 * helper::from_lval_const); + static_assert(T{std::in_place, 13}.error_or(helper(helper::list_t{7.0}, 3)).v == 7 * 3 * helper::from_rval); + + SUCCEED(); + } + } + } + } + + SECTION("monadic functions") + { + SECTION("and_then") + { + SECTION("value") + { + using T = expected; + constexpr auto fn + = [](auto &&a) constexpr -> expected { return helper(std::forward(a)).v * 2; }; + + T a(7); + CHECK(a.and_then(fn).value() == 7 * 2 * helper::from_lval); + CHECK(std::as_const(a).and_then(fn).value() == 7 * 2 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).and_then(fn).value() == 7 * 2 * helper::from_rval_const); + CHECK(std::move(a).and_then(fn).value() == 7 * 2 * helper::from_rval); + } + + SECTION("error") + { + using T = expected; + constexpr auto fn = [](auto &&) constexpr -> expected { return {0}; }; + + T a(unexpect, 11); + CHECK(a.and_then(fn).error().v == 11 * helper::from_lval); + CHECK(std::as_const(a).and_then(fn).error().v == 11 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).and_then(fn).error().v == 11 * helper::from_rval_const); + CHECK(std::move(a).and_then(fn).error().v == 11 * helper::from_rval); + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + constexpr auto fn + = [](auto &&a) constexpr -> expected { return helper(std::forward(a)).v * 3; }; + + SECTION("lval const") + { + { + constexpr T a(std::in_place, {3.0}, 5); + static_assert(a.and_then(fn).value() == 3 * 3 * 5 * helper::from_lval_const); + } + + { + constexpr T a(unexpect, Error::file_not_found); + static_assert(a.and_then(fn).error() == Error::file_not_found); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{std::in_place, {3.0}, 5}.and_then(fn) == 3 * 3 * 5 * helper::from_rval); + static_assert(T{unexpect, Error::file_not_found}.and_then(fn).error() == Error::file_not_found); + + SUCCEED(); + } + } + } + + SECTION("or_else") + { + SECTION("error") + { + using T = expected; + constexpr auto fn = [](auto &&a) constexpr -> expected { + return unexpected(helper(std::forward(a)).v * 3); + }; + + T a(unexpect, 5); + CHECK(a.or_else(fn).error() == 5 * 3 * helper::from_lval); + CHECK(std::as_const(a).or_else(fn).error() == 5 * 3 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).or_else(fn).error() == 5 * 3 * helper::from_rval_const); + CHECK(std::move(a).or_else(fn).error() == 5 * 3 * helper::from_rval); + } + + SECTION("value") + { + using T = expected; + constexpr auto fn = [](auto &&) constexpr -> expected { return {0}; }; + + T a(13); + CHECK(a.or_else(fn).value().v == 13 * helper::from_lval); + CHECK(std::as_const(a).or_else(fn).value().v == 13 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).or_else(fn).value().v == 13 * helper::from_rval_const); + CHECK(std::move(a).or_else(fn).value().v == 13 * helper::from_rval); + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + constexpr auto fn + = [](auto &&a) constexpr -> expected { return helper(std::forward(a)).v * 3; }; + + SECTION("lval const") + { + { + constexpr T a(std::in_place, 5); + static_assert(a.or_else(fn).value() == 5); + } + + { + constexpr T a(unexpect, {3.0}, 5); + static_assert(a.or_else(fn).value() == 3 * 3 * 5 * helper::from_lval_const); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{unexpect, {3.0}, 5}.or_else(fn).value() == 3 * 3 * 5 * helper::from_rval); + static_assert(T{std::in_place, 13}.or_else(fn).value() == 13); + + SUCCEED(); + } + } + } + + SECTION("transform") + { + SECTION("value") + { + using T = expected; + constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 2; }; + + T a(7); + CHECK(a.transform(fn).value() == 7 * 2 * helper::from_lval); + CHECK(std::as_const(a).transform(fn).value() == 7 * 2 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).transform(fn).value() == 7 * 2 * helper::from_rval_const); + CHECK(std::move(a).transform(fn).value() == 7 * 2 * helper::from_rval); + CHECK(a.transform([](auto &&) {}).has_value()); + } + + SECTION("error") + { + using T = expected; + constexpr auto fn = [](auto &&) constexpr -> int { return 0; }; + + T a(unexpect, 11); + CHECK(a.transform(fn).error().v == 11 * helper::from_lval); + CHECK(std::as_const(a).transform(fn).error().v == 11 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).transform(fn).error().v == 11 * helper::from_rval_const); + CHECK(std::move(a).transform(fn).error().v == 11 * helper::from_rval); + CHECK(a.transform([](auto &&) {}).error().v == 11 * helper::from_lval); + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; + + SECTION("lval const") + { + { + constexpr T a(std::in_place, {3.0}, 5); + static_assert(a.transform(fn).value() == 3 * 3 * 5 * helper::from_lval_const); + } + + { + constexpr T a(unexpect, Error::file_not_found); + static_assert(a.transform(fn).error() == Error::file_not_found); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{std::in_place, {3.0}, 5}.transform(fn) == 3 * 3 * 5 * helper::from_rval); + static_assert(T{unexpect, Error::file_not_found}.transform(fn).error() == Error::file_not_found); + + SUCCEED(); + } + } + } + + SECTION("transform_error") + { + SECTION("error") + { + using T = expected; + constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; + + T a(unexpect, 5); + CHECK(a.transform_error(fn).error() == 5 * 3 * helper::from_lval); + CHECK(std::as_const(a).transform_error(fn).error() == 5 * 3 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).transform_error(fn).error() == 5 * 3 * helper::from_rval_const); + CHECK(std::move(a).transform_error(fn).error() == 5 * 3 * helper::from_rval); + } + + SECTION("value") + { + using T = expected; + constexpr auto fn = [](auto &&) constexpr -> int { return 0; }; + + T a(13); + CHECK(a.transform_error(fn).value().v == 13 * helper::from_lval); + CHECK(std::as_const(a).transform_error(fn).value().v == 13 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).transform_error(fn).value().v == 13 * helper::from_rval_const); + CHECK(std::move(a).transform_error(fn).value().v == 13 * helper::from_rval); + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; + + SECTION("lval const") + { + { + constexpr T a(std::in_place, 5); + static_assert(a.transform_error(fn).value() == 5); + } + + { + constexpr T a(unexpect, {3.0}, 5); + static_assert(a.transform_error(fn).error() == 3 * 3 * 5 * helper::from_lval_const); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{unexpect, {3.0}, 5}.transform_error(fn).error() == 3 * 3 * 5 * helper::from_rval); + static_assert(T{std::in_place, 13}.transform_error(fn).value() == 13); + + SUCCEED(); + } + } + } + } + + SECTION("equality operators") + { + SECTION("operand expected") + { + using T = expected; + using U = expected; + + SECTION("value and error") + { + T const t1{std::in_place, {12.0}}; + U const u1{unexpect, false}; + CHECK(not(t1 == u1)); + CHECK((t1 != u1)); + + constexpr T t2{unexpect, 12}; + constexpr U u2{std::in_place, helper::list_t(), 13}; + static_assert(not(t2 == u2)); + static_assert(t2 != u2); + } + + SECTION("value") + { + SECTION("same type") + { + T const t1{std::in_place, {12.0}}; + U const u1{std::in_place, {3.0}}; + CHECK(not(t1 == u1)); + CHECK((t1 != u1)); + + constexpr T t2{std::in_place, {3.0}, 4}; + constexpr U u2{std::in_place, {3.0}, 2, 2}; + static_assert(t2 == u2); + static_assert(not(t2 != u2)); + CHECK((t1 == t2)); + } + + SECTION("different types") + { + using V = expected; + using W = expected; + static_assert(V{12} == W{12.0}); + static_assert(V{15} != W{12.0}); + + SUCCEED(); + } + } + + SECTION("error") + { + SECTION("same type") + { + using V = expected; + using W = expected; + + V const v1{unexpect, {12.0}}; + W const w1{unexpect, {3.0}}; + CHECK(not(v1 == w1)); + CHECK((v1 != w1)); + + constexpr V v2{unexpect, {3.0}, 4}; + constexpr W w2{unexpect, {3.0}, 2, 2}; + static_assert(v2 == w2); + static_assert(not(v2 != w2)); + CHECK((v1 == v2)); + } + + SECTION("different types") + { + using V = expected; + using W = expected; + static_assert(V{unexpect, 12} == W{unexpect, 12.0}); + static_assert(V{unexpect, 15} != W{unexpect, 12.0}); + + SUCCEED(); + } + } + } + + SECTION("operand value") + { + SECTION("same type") + { + using T = expected; + T const t1{std::in_place, {12.0}}; + helper const u1{3.0}; + helper const v1{3, 4}; + CHECK(not(t1 == u1)); + CHECK((t1 != u1)); + CHECK((t1 == v1)); + + constexpr T t2{std::in_place, {3.0}, 4}; + constexpr helper u2{helper::list_t(), 3, 2, 2}; + static_assert(t2 == u2); + static_assert(not(t2 != u2)); + CHECK((t1 == t2)); + } + + SECTION("different types") + { + using T = expected; + constexpr T t1{std::in_place, 12}; + constexpr double u1 = 12.0; + static_assert(t1 == u1); + static_assert(T{15.0} != u1); + + SUCCEED(); + }; + + SUCCEED(); + } + + SECTION("operand unexpected") + { + SECTION("same type") + { + using T = expected; + using U = unexpected; + T const t1{unexpect, {12.0}}; + U const u1{std::in_place, {3.0}}; + U const v1{std::in_place, {3.0, 4.0}}; + CHECK(not(t1 == u1)); + CHECK((t1 == v1)); + CHECK((t1 != u1)); + + constexpr T t2{unexpect, {3.0}, 4}; + constexpr U u2{std::in_place, helper::list_t(), 3, 2, 2}; + static_assert(t2 == u2); + static_assert(not(t2 != u2)); + CHECK((t1 == t2)); + } + + SECTION("different types") + { + using T = expected; + using U = unexpected; + constexpr T t1{unexpect, 1}; + static_assert(t1 == U{Error::unknown}); + static_assert(t1 != U{Error::file_not_found}); + + SUCCEED(); + } + } + } +} + +TEST_CASE("expected void", "[expected_void][polyfill]") +{ +#ifndef PFN_TEST_VALIDATION + constexpr bool extension = true; +#else + constexpr bool extension = false; +#endif + + SECTION("constructors") + { + SECTION("default unavailable") + { + static_assert(not std::is_default_constructible_v); // prerequisite + static_assert(not std::is_default_constructible_v>); + static_assert(std::is_default_constructible_v>); + SUCCEED(); + } + + SECTION("default trivial") + { + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + + constexpr T a; + static_assert(a.has_value()); + SUCCEED(); + } + + struct A { + constexpr A() noexcept(true) {} + constexpr bool operator==(A const &) const = default; + + private: + int v = 12; + }; + + SECTION("default noexcept(true) from value type") + { + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + + constexpr T a; + static_assert(a.has_value()); + + T b; + CHECK(b.has_value()); + } + + struct B { + constexpr B() noexcept(false) {} + int v = 42; + }; + + SECTION("default ignore noexcept from error type") + { + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + SUCCEED(); + } + + struct C { + C() noexcept(false) { throw 7; } + }; + + SECTION("default exception thrown") + { + // not a problem if error type ctor is throwing + using T = expected; + static_assert(std::is_default_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); + + T b; + CHECK(b.has_value()); + } + + SECTION("from unexpected rval") + { + using T = expected; + static_assert(std::is_constructible_v>); + static_assert(not std::is_nothrow_constructible_v>); + static_assert(std::is_constructible_v>); + static_assert(not extension || std::is_nothrow_constructible_v>); + + constexpr expected a(unexpected(true)); + static_assert(a.error() == 1); + + T const b(unexpected(5)); + CHECK(b.error().v == 5); + } + + SECTION("from unexpected lval const") + { + using T = expected; + constexpr auto g1 = unexpected(5); + constexpr expected a(g1); + static_assert(a.error() == 5); + + T const b(g1); + CHECK(b.error().v == 5); + } + + SECTION("with in_place") + { + using T = expected; + static_assert(std::is_constructible_v); + static_assert(std::is_nothrow_constructible_v); + + T const b(std::in_place); + CHECK(b.has_value()); + } + } + + SECTION("copy, move and dtor") + { + SECTION("trivial") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(std::is_trivially_copy_constructible_v); + static_assert(not extension || std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(not extension || std::is_nothrow_move_constructible_v); + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + constexpr T a; + constexpr T b = a; + static_assert(b.has_value()); + + { + T a(std::in_place); + T b = a; + CHECK(b.has_value()); + + T c = std::move(a); + CHECK(c.has_value()); + } + } + + SECTION("non-trivial error type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not extension || std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); // required + static_assert(not std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + { + T a(unexpect, 33); + T b = a; // no overload for lval + CHECK(not b.has_value()); + CHECK(b.error().v == 33 * helper::from_lval_const); + + T c = std::as_const(a); + CHECK(not b.has_value()); + CHECK(c.error().v == 33 * helper::from_lval_const); + + T d = std::move(std::as_const(a)); // no overload for lval const + CHECK(not b.has_value()); + CHECK(d.error().v == 33 * helper::from_lval_const); + + T e = std::move(a); + CHECK(not b.has_value()); + CHECK(e.error().v == 33 * helper::from_rval); + } + } + + struct B { + int v; + constexpr B(int v) : v(v) {} + constexpr B(B const &s) noexcept(false) : v(s.v) {}; + constexpr B(B &&s) noexcept(false) : v(s.v) {}; + }; + + SECTION("noexcept(false) from error type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); // required + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_nothrow_destructible_v); + + constexpr T a(unexpect, 23); + constexpr T b = a; + static_assert(not b.has_value() && a.error().v == b.error().v); + + { + T const a(unexpect, 29); + T b = a; + CHECK(not b.has_value()); + CHECK(b.error().v == 29); + + T c = std::move(a); + CHECK(not c.has_value()); + CHECK(c.error().v == 29); + } + } + + struct C { + constexpr C() noexcept(true) {}; + constexpr C(C const &) noexcept(true) {}; // WORKAROUND:MSVC + constexpr ~C() noexcept(false) {}; + }; + + SECTION("noexcept(false) dtor error type") + { + using T = expected; + static_assert(std::is_copy_constructible_v); + static_assert(not std::is_trivially_copy_constructible_v); + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_trivially_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); // required + static_assert(not std::is_trivially_destructible_v); + + constexpr T a(unexpect); + constexpr T b = a; + static_assert(not b.has_value()); + + { + T const a(unexpect); + T b = a; + CHECK(not b.has_value()); + } + } + } + + SECTION("assignment") + { + using M = helper_t<2>; // nothrow move constructible + using E = helper_t<3>; // may throw on move and copy + using C = helper_t<4>; // nothrow copy constructible + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + SECTION("from rval") + { + SECTION("value to value") + { + using T = expected; + + { + static_assert(std::is_nothrow_assignable_v); // required + + T a(std::in_place); + a = T(std::in_place); + CHECK(a.has_value()); + } + } + + SECTION("value to error") + { + SECTION("nothrow move") + { + using T = expected; + static_assert(std::is_nothrow_assignable_v); // required + + { + T a(std::in_place); + a = T(unexpect, 5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + static_assert(not extension || std::is_nothrow_assignable_v &&>); + T a(std::in_place); + a = unexpected(5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place); + try { + a = unexpected({0.0}); + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + } + + SECTION("throwing") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); // required + + { + T a(std::in_place); + a = T(unexpect, 5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place); + try { + a = T(unexpect, {0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.has_value()); + } + } + + { + static_assert(not std::is_nothrow_assignable_v &&>); + T a(std::in_place); + a = unexpected(5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place); + try { + a = unexpected({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.has_value()); + } + } + } + + SECTION("nothrow copy") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); // required + + { + T a(std::in_place); + a = T(unexpect, 5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place); + try { + a = T(unexpect, {0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.has_value()); + } + } + + { + static_assert(not std::is_nothrow_assignable_v &&>); + T a(std::in_place); + a = unexpected(5); + CHECK(a.error().v == 5 * helper::from_rval); + } + + { + T a(std::in_place); + try { + a = unexpected({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.has_value()); + } + } + } + } + + SECTION("error to value") + { + SECTION("nothrow move") + { + using T = expected; + static_assert(std::is_nothrow_assignable_v); // required + + { + T a(unexpect, Error::file_not_found); + a = T(std::in_place); + CHECK(a.has_value()); + } + + { + T a(unexpect, Error::file_not_found); + try { + a = T(std::in_place); + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + } + } + + SECTION("error to error") + { + using T = expected; + static_assert(std::is_nothrow_assignable_v); // required + + T a(unexpect, 3); + a = T(unexpect, 5); + CHECK(a.error().v == 5 * helper::from_rval); + + a = unexpected(7); + CHECK(a.error().v == 7 * helper::from_rval); + } + + SECTION("constexpr") + { + using T = expected; + + SECTION("from error") + { + constexpr auto fn = [](T &&v) constexpr -> T { + T tmp{unexpect, Error::unknown}; + tmp = std::move(v); + return tmp; + }; + + constexpr T a = fn(T(unexpect, Error::file_not_found)); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(T(std::in_place)); + static_assert(b.has_value()); + + SUCCEED(); + } + + SECTION("from value") + { + { + constexpr auto fn = [](T &&v) constexpr -> T { + T tmp{std::in_place}; + tmp = std::move(v); + return tmp; + }; + + constexpr T a = fn(T(unexpect, Error::file_not_found)); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(T(std::in_place)); + static_assert(b.has_value()); + + SUCCEED(); + } + } + } + } + + SECTION("from lval const") + { + SECTION("value to error") + { + SECTION("nothrow move") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + + { + T a(std::in_place); + T const b(unexpect, 5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place); + try { + T const b(unexpect, {0.0}); + a = b; // copy construction on a side of `New tmp` will throw + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.has_value()); + } + } + + { + static_assert(not std::is_nothrow_assignable_v const &>); + T a(std::in_place); + unexpected const b(5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place); + try { + unexpected const b({0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.has_value()); + } + } + } + + SECTION("throwing") + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + + { + T a(std::in_place); + T const b(unexpect, 5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place); + try { + T const b(unexpect, {0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.has_value()); + } + } + + { + static_assert(not std::is_nothrow_assignable_v const &>); + T a(std::in_place); + unexpected const b(5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place); + try { + unexpected const b({0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.has_value()); + } + } + } + + SECTION("nothrow copy") + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + + { + T a(std::in_place); + T const b(unexpect, 5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place); + try { + T const b(unexpect, {0.0}); + a = b; + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + + { + static_assert(not extension || std::is_nothrow_assignable_v const &>); + T a(std::in_place); + unexpected const b(5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + } + + { + T a(std::in_place); + try { + unexpected b(std::in_place, {0.0}); + a = b; + SUCCEED(); + } catch (std::runtime_error const &e) { + FAIL(); + } + } + } + } + + SECTION("error to value") + { + using T = expected; + static_assert(std::is_nothrow_assignable_v); + + { + T a(unexpect, Error::file_not_found); + T const b(std::in_place); + a = b; + CHECK(a.has_value()); + } + } + + SECTION("error to error") + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + + T a(unexpect, 3); + T const b(unexpect, 5); + a = b; + CHECK(a.error().v == 5 * helper::from_lval_const); + + unexpected const c(7); + a = c; + CHECK(a.error().v == 7 * helper::from_lval_const); + } + } + + SECTION("constexpr") + { + using T = expected; + constexpr T c{unexpect, Error::file_not_found}; + constexpr T d{std::in_place}; + + SECTION("from error") + { + constexpr auto fn = [](T const &v) constexpr -> T { + T tmp{unexpect, Error::unknown}; + tmp = v; + return tmp; + }; + + constexpr T a = fn(c); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(d); + static_assert(b.has_value()); + + SUCCEED(); + } + + SECTION("from value") + { + { + constexpr auto fn = [](T const &v) constexpr -> T { + T tmp{std::in_place}; + tmp = v; + return tmp; + }; + + constexpr T a = fn(c); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(d); + static_assert(b.has_value()); + + SUCCEED(); + } + } + } + } + + SECTION("emplace") + { + using T = expected; + SECTION("value to value") + { + T a(std::in_place); + a.emplace(); + CHECK(a.has_value()); + } + + SECTION("error to value") + { + T a(unexpect, Error::file_not_found); + a.emplace(); + CHECK(a.has_value()); + } + + SECTION("constexpr") + { + using T = expected; + + SECTION("from error") + { + constexpr auto fn = []() constexpr -> T { + T tmp{unexpect, Error::unknown}; + tmp.emplace(); + return tmp; + }; + + constexpr T a = fn(); + static_assert(a.has_value()); + + SUCCEED(); + } + + SECTION("from value") + { + { + constexpr auto fn = []() constexpr -> T { + T tmp{std::in_place}; + tmp.emplace(); + return tmp; + }; + + constexpr T a = fn(); + static_assert(a.has_value()); + + SUCCEED(); + } + } + + SECTION("throwing constructor") + { + constexpr auto fn = [](auto &&...args) constexpr -> bool { + return requires { std::declval().emplace(std::forward(args)...); }; + }; + + static_assert(not fn(1, 2)); + + SUCCEED(); + } + } + } + + SECTION("swap") + { + SECTION("non-swappable") + { + struct A : non_swappable {}; + static_assert(not std::is_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + static_assert(not extension || not is_swappable>); + + SUCCEED(); + } + + SECTION("non-move-constructible") + { + struct A : swappable { + A(A &&) = delete; + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(not std::is_move_constructible_v); + + static_assert(not is_swappable>); + + SUCCEED(); + } + + SECTION("non-nothrow-move-constructible") + { + struct A : swappable { + A(A &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + + SUCCEED(); + } + + SECTION("nothrow-swappable non-nothrow-move-constructible") + { + struct A : nothrow_swappable { + A(A &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + + SUCCEED(); + } + + SECTION("swappabla, non-nothrow-move-constructible") + { + struct A : swappable { + A(A &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + + SUCCEED(); + } + + SECTION("nothrow-swappabla, non-nothrow-move-constructible") + { + struct A : nothrow_swappable { + A(A &&) noexcept(false) {} + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + + SUCCEED(); + } + + SECTION("nothrow-swappabla, nothrow-move-constructible") + { + struct A : nothrow_swappable { + A(A &&) noexcept(true) = default; + }; + static_assert(std::is_swappable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + + SUCCEED(); + } + + SECTION("swap same") + { + SECTION("value") + { + using T = expected; + T a; + T b; + swap(a, b); + CHECK(a.has_value()); + CHECK(b.has_value()); + } + + SECTION("error") + { + using T = expected; + T a(unexpect, 17); + T b(unexpect, 23); + swap(a, b); + CHECK(a.error().v == 23 * helper::swapped); + CHECK(b.error().v == 17 * helper::swapped); + } + } + + SECTION("swap error/value") + { + using T = expected>; + T a(unexpect, 19); + T b; + swap(a, b); + CHECK(a.has_value()); + CHECK(b.error().v == 19 * helper::from_rval); + } + + SECTION("swap value/error") + { + SECTION("nothrow") + { + using T = expected>; + T a; + T b(unexpect, 29); + swap(a, b); + CHECK(a.error().v == 29 * helper::from_rval); + CHECK(b.has_value()); + } + + static_assert(not std::is_nothrow_move_constructible_v>); + static_assert(std::is_nothrow_move_constructible_v>); + + SECTION("throw error") + { + using T = expected>; + SECTION("happy path") + { + T a; + T b(unexpect, 11); + swap(a, b); + CHECK(a.error().v == 11 * helper::from_rval); + CHECK(b.has_value()); + } + + SECTION("exception") + { + T a(std::in_place); + T b(unexpect, {0.0}); + try { + swap(a, b); + FAIL(); + } catch (std::runtime_error const &) { + CHECK(a.has_value()); + CHECK(b.error().v == 0); + } + } + } + } + + SECTION("constexpr") + { + using T = expected; + + SECTION("to error") + { + constexpr auto fn = [](T &&v) constexpr -> T { + T tmp{unexpect, Error::unknown}; + swap(tmp, v); + return v; + }; + + constexpr T a = fn(T(unexpect, Error::file_not_found)); + static_assert(a.error() == Error::unknown); + + constexpr T b = fn(T()); + static_assert(b.error() == Error::unknown); + + SUCCEED(); + } + + SECTION("to value") + { + constexpr auto fn = [](T &&v) constexpr -> T { + T tmp{std::in_place}; + swap(tmp, v); + return v; + }; + + constexpr T a = fn(T(unexpect, Error::file_not_found)); + static_assert(a.has_value()); + + constexpr T b = fn(T()); + static_assert(b.has_value()); + + SUCCEED(); + } + } + } + + SECTION("accessors") + { + SECTION("value") + { + using T = expected; + + T a; + static_assert(std::is_same_v); + SUCCEED(); + + { + T a{unexpect, Error::file_not_found}; + CHECK(!a); + + try { + a.value(); + FAIL(); + } catch (bad_expected_access const &e) { + CHECK(e.error() == Error::file_not_found); + } + + try { + std::as_const(a).value(); + FAIL(); + } catch (bad_expected_access const &e) { + CHECK(e.error() == Error::file_not_found); + } + + try { + std::move(std::as_const(a)).value(); + FAIL(); + } catch (bad_expected_access const &e) { + CHECK(e.error() == Error::file_not_found); + } + + try { + std::move(a).value(); + FAIL(); + } catch (bad_expected_access const &e) { + CHECK(e.error() == Error::file_not_found); + } + } + } + + SECTION("error") + { + using T = expected; + + T a{unexpect, 17}; + CHECK(a.error().v == 17); + CHECK(std::as_const(a).error().v == 17); + CHECK(std::move(std::as_const(a)).error().v == 17); + CHECK(std::move(a).error().v == 17); + + { + helper b{1}; + CHECK((b = a.error()).v == 17 * helper::from_lval); + CHECK((b = std::as_const(a).error()).v == 17 * helper::from_lval_const); + CHECK((b = std::move(std::as_const(a)).error()).v == 17 * helper::from_rval_const); + CHECK((b = std::move(a).error()).v == 17 * helper::from_rval); + } + } + + SECTION("error_or") + { + using T = expected; + SECTION("noexcept extension") + { + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + } + + SECTION("error") + { + T a(unexpect, 7); + CHECK(a.error_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::as_const(a).error_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::move(std::as_const(a)).error_or(0) == helper(7 * helper::from_lval_const)); + CHECK(std::move(a).error_or(0) == helper(7 * helper::from_rval)); + } + + SECTION("value") + { + { + T a{}; + CHECK(a.error_or(17) == helper(17)); + CHECK(std::move(a).error_or(5) == helper(5)); + } + + { + T const a{}; + helper b(11); + CHECK(a.error_or(b) == helper(11 * helper::from_lval)); + CHECK(a.error_or(std::as_const(b)) == helper(11 * helper::from_lval_const)); + CHECK(a.error_or(std::move(std::as_const(b))) == helper(11 * helper::from_rval_const)); + CHECK(a.error_or(std::move(b)) == helper(11 * helper::from_rval)); + } + } + + SECTION("noexcept extension") + { + { + using T = expected>; + static_assert(not std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); + + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + } + + { + using T = expected>; + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); + + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + } + + SUCCEED(); + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + + SECTION("lval const") + { + { + constexpr T a(unexpect, {3.0}, 5); + static_assert(a.error_or(c).v == 3 * 5 * helper::from_lval_const); + } + + { + constexpr T a(std::in_place); + static_assert(a.error_or(c).v == 7 * helper::from_lval_const); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{unexpect, {3.0}, 5}.error_or(c).v == 3 * 5 * helper::from_rval); + static_assert(T{std::in_place}.error_or(c).v == 7 * helper::from_lval_const); + static_assert(T{std::in_place}.error_or(helper(helper::list_t{7.0}, 3)).v == 7 * 3 * helper::from_rval); + + SUCCEED(); + } + } + } + } + + SECTION("monadic functions") + { + SECTION("and_then") + { + SECTION("value") + { + using T = expected; + constexpr auto fn = []() constexpr -> expected { return {2}; }; + + T a; + CHECK(a.and_then(fn).value() == 2); + } + + SECTION("error") + { + using T = expected; + constexpr auto fn = []() constexpr -> expected { return {0}; }; + + T a(unexpect, 11); + CHECK(a.and_then(fn).error().v == 11 * helper::from_lval); + CHECK(std::as_const(a).and_then(fn).error().v == 11 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).and_then(fn).error().v == 11 * helper::from_rval_const); + CHECK(std::move(a).and_then(fn).error().v == 11 * helper::from_rval); + } + + SECTION("constexpr") + { + using T = expected; + constexpr auto fn = []() constexpr -> expected { return {3}; }; + + SECTION("lval const") + { + { + constexpr T a(std::in_place); + static_assert(a.and_then(fn).value() == 3); + } + + { + constexpr T a(unexpect, Error::file_not_found); + static_assert(a.and_then(fn).error() == Error::file_not_found); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{std::in_place}.and_then(fn) == 3); + static_assert(T{unexpect, Error::file_not_found}.and_then(fn).error() == Error::file_not_found); + + SUCCEED(); + } + } + } + + SECTION("or_else") + { + SECTION("error") + { + using T = expected; + constexpr auto fn = [](auto &&a) constexpr -> expected { + return expected{unexpect, helper(std::forward(a)).v * 3}; + }; + + T a(unexpect, 5); + CHECK(a.or_else(fn).error() == 5 * 3 * helper::from_lval); + CHECK(std::as_const(a).or_else(fn).error() == 5 * 3 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).or_else(fn).error() == 5 * 3 * helper::from_rval_const); + CHECK(std::move(a).or_else(fn).error() == 5 * 3 * helper::from_rval); + } + + SECTION("value") + { + using T = expected; + constexpr auto fn = [](auto &&) constexpr -> expected { return expected{unexpect, 13}; }; + + T a; + CHECK(a.or_else(fn).has_value()); + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + constexpr auto fn = [](auto &&a) constexpr -> expected { + return expected{unexpect, helper(std::forward(a)).v * 3}; + }; + + SECTION("lval const") + { + { + constexpr T a(std::in_place); + static_assert(a.or_else(fn).has_value()); + } + + { + constexpr T a(unexpect, {3.0}, 5); + static_assert(a.or_else(fn).error() == 3 * 3 * 5 * helper::from_lval_const); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{unexpect, {3.0}, 5}.or_else(fn).error() == 3 * 3 * 5 * helper::from_rval); + static_assert(T{std::in_place}.or_else(fn).has_value()); + + SUCCEED(); + } + } + } + + SECTION("transform") + { + SECTION("value") + { + using T = expected; + constexpr auto fn = []() constexpr -> int { return 2; }; + + T a; + CHECK(a.transform(fn).value() == 2); + CHECK(a.transform([]() {}).has_value()); + } + + SECTION("error") + { + using T = expected; + constexpr auto fn = []() constexpr -> int { return 0; }; + + T a(unexpect, 11); + CHECK(a.transform(fn).error().v == 11 * helper::from_lval); + CHECK(std::as_const(a).transform(fn).error().v == 11 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).transform(fn).error().v == 11 * helper::from_rval_const); + CHECK(std::move(a).transform(fn).error().v == 11 * helper::from_rval); + CHECK(a.transform([]() {}).error().v == 11 * helper::from_lval); + } + + SECTION("constexpr") + { + using T = expected; + constexpr auto fn = []() constexpr -> int { return 3; }; + + SECTION("lval const") + { + { + constexpr T a(std::in_place); + static_assert(a.transform(fn).value() == 3); + } + + { + constexpr T a(unexpect, Error::file_not_found); + static_assert(a.transform(fn).error() == Error::file_not_found); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{std::in_place}.transform(fn) == 3); + static_assert(T{unexpect, Error::file_not_found}.transform(fn).error() == Error::file_not_found); + + SUCCEED(); + } + } + } + + SECTION("transform_error") + { + SECTION("error") + { + using T = expected; + constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; + + T a(unexpect, 5); + CHECK(a.transform_error(fn).error() == 5 * 3 * helper::from_lval); + CHECK(std::as_const(a).transform_error(fn).error() == 5 * 3 * helper::from_lval_const); + CHECK(std::move(std::as_const(a)).transform_error(fn).error() == 5 * 3 * helper::from_rval_const); + CHECK(std::move(a).transform_error(fn).error() == 5 * 3 * helper::from_rval); + } + + SECTION("value") + { + using T = expected; + constexpr auto fn = [](auto &&) constexpr -> int { return 0; }; + + T a{}; + CHECK(a.transform_error(fn).has_value()); + } + + SECTION("constexpr") + { + using T = expected; + constexpr helper c{helper::list_t(), 7}; + constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; + + SECTION("lval const") + { + { + constexpr T a(std::in_place); + static_assert(a.transform_error(fn).has_value()); + } + + { + constexpr T a(unexpect, {3.0}, 5); + static_assert(a.transform_error(fn).error() == 3 * 3 * 5 * helper::from_lval_const); + } + + SUCCEED(); + } + + SECTION("rval") + { + static_assert(T{unexpect, {3.0}, 5}.transform_error(fn).error() == 3 * 3 * 5 * helper::from_rval); + static_assert(T{std::in_place}.transform_error(fn).has_value()); + + SUCCEED(); + } + } + } + } + + SECTION("equality operators") + { + SECTION("operand expected") + { + using T = expected; + using U = expected; + + SECTION("value and error") + { + T const t1{std::in_place}; + U const u1{unexpect, false}; + CHECK(not(t1 == u1)); + CHECK((t1 != u1)); + + constexpr T t2{unexpect, 12}; + constexpr U u2{std::in_place}; + static_assert(not(t2 == u2)); + static_assert(t2 != u2); + } + + SECTION("value") + { + T const t1{std::in_place}; + U const u1{std::in_place}; + CHECK((t1 == u1)); + } + + SECTION("error") + { + SECTION("same type") + { + using V = expected; + using W = expected; + + V const v1{unexpect, {12.0}}; + W const w1{unexpect, {3.0}}; + CHECK(not(v1 == w1)); + CHECK((v1 != w1)); + + constexpr V v2{unexpect, {3.0}, 4}; + constexpr W w2{unexpect, {3.0}, 2, 2}; + static_assert(v2 == w2); + static_assert(not(v2 != w2)); + CHECK((v1 == v2)); + } + + SECTION("different types") + { + using V = expected; + using W = expected; + static_assert(V{unexpect, 12} == W{unexpect, 12.0}); + static_assert(V{unexpect, 15} != W{unexpect, 12.0}); + + SUCCEED(); + } + } + } + + SECTION("operand unexpected") + { + SECTION("same type") + { + using T = expected; + using U = unexpected; + T const t1{unexpect, {12.0}}; + U const u1{std::in_place, {3.0}}; + U const v1{std::in_place, {3.0, 4.0}}; + CHECK(not(t1 == u1)); + CHECK((t1 == v1)); + CHECK((t1 != u1)); + + constexpr T t2{unexpect, {3.0}, 4}; + constexpr U u2{std::in_place, helper::list_t(), 3, 2, 2}; + static_assert(t2 == u2); + static_assert(not(t2 != u2)); + CHECK((t1 == t2)); + } + + SECTION("different types") + { + using T = expected; + using U = unexpected; + constexpr T t1{unexpect, 1}; + static_assert(t1 == U{Error::unknown}); + static_assert(t1 != U{Error::file_not_found}); + + SUCCEED(); + } + } + } +} diff --git a/tests/util/helper_types.hpp b/tests/util/helper_types.hpp new file mode 100644 index 00000000..2ff60899 --- /dev/null +++ b/tests/util/helper_types.hpp @@ -0,0 +1,164 @@ +// Copyright (c) 2025 Bronek Kozicki +// +// Distributed under the ISC License. See accompanying file LICENSE.md +// or copy at https://opensource.org/licenses/ISC + +#include +#include +#include +#include +#include +#include + +template struct helper_t { + static inline int state = 0; + + int v = {}; + + // Use prime numbers to record Foo states in witness + enum { + from_lval = 53, // + from_lval_const = 59, + from_rval = 61, + from_rval_const = 67, + swapped = 97 + }; + + // No default constructor + helper_t() = delete; + + constexpr ~helper_t() noexcept {}; + + constexpr bool operator==(helper_t const &) const noexcept = default; + + // Assignment operators will multiply witness by a prime + constexpr helper_t &operator=(helper_t &o) noexcept + { + v = o.v; + v *= from_lval; + return *this; + } + + constexpr helper_t &operator=(helper_t const &o) noexcept + { + v = o.v; + v *= from_lval_const; + return *this; + } + + constexpr helper_t &operator=(helper_t &&o) noexcept + { + v = o.v; + v *= from_rval; + return *this; + } + + constexpr helper_t &operator=(helper_t const &&o) noexcept + { + v = o.v; + v *= from_rval_const; + return *this; + } + + // See table below + constexpr helper_t(helper_t &o) noexcept : v(o.v) { v *= from_lval; } + constexpr helper_t(helper_t const &o) noexcept(V < 2 || V >= 4) : v(o.v) + { + v *= from_lval_const; + if constexpr (V >= 2 && V < 4) { + if (v == 0) + throw std::runtime_error("invalid input"); + } + } + constexpr helper_t(helper_t &&o) noexcept(V < 3 || V >= 5) + requires(V < 30) + : v(o.v) + { + v *= from_rval; + if constexpr (V >= 3 && V < 5) { + if (v == 0) + throw std::runtime_error("invalid input"); + } + } + helper_t(helper_t &&o) noexcept(V < 33 || V >= 35) + requires(V >= 30) + : v(o.v) + { + v *= from_rval; + state += v; + if constexpr (V >= 33 && V < 35) { + if (v == 0) + throw std::runtime_error("invalid input"); + } + } + constexpr helper_t(helper_t const &&o) noexcept : v(o.v) { v *= from_rval_const; } + + // The intent of non-constexpr constructors is to make sure that they are never optimized away, + // thus ensuring that any code which relies on them in tests will show up in coverage reports. + helper_t(std::integral auto... a) noexcept(V >= 8) + requires(sizeof...(a) > 0) // intentionally implicit when sizeof...(a) == 1 + : v((1 * ... * a)) + { + if constexpr (V < 8) { + if (v == 0) + throw std::runtime_error("invalid input"); + } + state += v; + } + + using list_t = std::initializer_list; + + helper_t(list_t list) noexcept(true) : v(init(list)) { state += v; } + + // Potentially throwing constructor + constexpr helper_t(list_t list, std::integral auto... a) noexcept(true) + requires(sizeof...(a) > 0) + : v(init(list, a...)) // + { + } + + // ... and the actual exception being thrown + static constexpr int init(list_t l, auto &&...a) noexcept + { + double ret = (1 * ... * a); + for (auto d : l) { + ret *= d; + } + return static_cast(ret); + } + + // Disable comparison operators; compare .v instead + friend bool operator==(helper_t, std::integral auto) = delete; + friend std::strong_ordering operator<=>(helper_t, helper_t) = delete; +}; + +// helper_t +// V is_nothrow_copy_constructible is_nothrow_move_constructible +// 0 1 1 +// 1 1 1 +// 2 0 1 +// 3 0 0 +// 4 1 0 +// 5 1 1 +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(not std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(not std::is_nothrow_copy_constructible_v>); +static_assert(not std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(not std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); + +// Swap will also multiply witness by a prime +template constexpr void swap(helper_t &l, helper_t &r) +{ + std::swap(l.v, r.v); + l.v *= l.swapped; + r.v *= r.swapped; +} + +using helper = helper_t<0>;