diff --git a/include/cpp2util.h b/include/cpp2util.h index dee5bd2a9..4aead2ae1 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -2078,6 +2078,93 @@ constexpr auto as( X&& x ) -> decltype(auto) { } + +//------------------------------------------------------------------------------------------------------------- +// std::expected is and as +// +#ifdef __cpp_lib_expected + +// is Type +// +template + requires std::is_same_v> +constexpr auto is(X const& x) -> bool +{ + return x.has_value(); +} + +template + requires std::is_same_v +constexpr auto is(std::expected const& x) -> bool +{ + return !x.has_value(); +} + +// is std::unexpected Type +// +template + requires ( + std::is_same_v> + && std::is_same_v> + ) +constexpr auto is(X const& x) -> bool +{ + return !x.has_value(); +} + + +// is Value +// +template +constexpr auto is(std::expected const& x, auto&& value) -> bool +{ + // Predicate case + if constexpr (requires{ bool{ value(x) }; }) { + return value(x); + } + else if constexpr (std::is_function_v || requires{ &value.operator(); }) { + return false; + } + + // Value case + else if constexpr (requires{ bool{ x.value() == value }; }) { + return x.has_value() && x.value() == value; + } + else { + return false; + } +} + + +// as +// +template + requires std::is_same_v> +constexpr auto as(X const& x) -> decltype(auto) +{ + return x.value(); +} + +// as std::unexpected +// +template + requires ( + std::is_same_v> + && std::is_same_v> + ) +constexpr auto as(X const& x) -> decltype(auto) +{ + // It's UB to call `error` if `has_value` is true. + if (x.has_value()) { + Throw( + std::runtime_error("Cannot cast 'expected' to 'unexpected' because it has a value"), + "Cannot cast 'expected' to 'unexpected' because it has a value"); + } + + return std::unexpected(x.error()); +} +#endif + } // impl diff --git a/regression-tests/pure2-expected-is-as.cpp2 b/regression-tests/pure2-expected-is-as.cpp2 new file mode 100644 index 000000000..d144b2770 --- /dev/null +++ b/regression-tests/pure2-expected-is-as.cpp2 @@ -0,0 +1,90 @@ +// `std::expected` requires C++23 so a dedicated test file is needed +// since only MSVC supports it at time of writing, and there's no #ifdef +// or `static if` support in Cpp2 (yet?). + +main: () -> int = { + + ex1: std::expected = (123); + ex2: std::expected = std::unexpected(-1); + ex3: std::expected = ("Expect the unexpected"); + + if ex1 is int { + std::cout << "ex1 is int\n"; + } + + if ex1 is bool { + std::cout << "BUG - ex1 is not a bool\n"; + return -1; + } + + if ex1 is void { + std::cout << "BUG - ex1 is not 'empty'\n"; + return -1; + } + + if ex1 is std::unexpected { + std::cout << "BUG - ex1 is not unexpected\n"; + return -1; + } + + if ex1 is 123 { + std::cout << "ex1 is 123\n"; + } + + if ex1 is 100 { + std::cout << "BUG - ex1's value is not 100\n"; + return -1; + } + + val1:= ex1 as int; + std::cout << "ex1 as int = " << val1 << "\n"; + + if ex2 is int { + std::cout << "BUG - ex2 is not an int\n"; + return -1; + } + + if ex2 is bool { + std::cout << "BUG - ex2 is not a bool\n"; + return -1; + } + + if ex2 is 123 { + std::cout << "BUG - ex2 does not have a value\n"; + return -1; + } + + if ex2 is std::unexpected { + std::cout << "ex2 is unexpected and error is: " << ex2.error() << "\n"; + } + + if ex2 is void { + std::cout << "ex2 is 'empty' aka unexpected and error is: " << ex2.error() << "\n"; + } + + ex2_err:= ex2 as std::unexpected; + std::cout << "ex2 as std::unexpected and error = " << ex2_err.error() << "\n"; + + test_inspect(ex1, "expected with value"); + test_inspect(ex2, "expected with unexpected"); + test_inspect(ex3, "expected with value"); + + return 0; +} + +test_inspect: ( x: _, msg: _ ) = { + + unwrap:= :(unexp: std::unexpected) -> _ = { + return unexp.error(); + }; + + std::cout + << "\n" << msg << "\n ..." + << inspect x -> std::string { + is int = "integer " + std::to_string(x as int); + is std::unexpected = "unexpected " + std::to_string(unwrap(x as std::unexpected)); + is std::string = "string " + x as std::string; + is _ = " no match"; + } + << "\n"; +} \ No newline at end of file diff --git a/regression-tests/test-results/msvc-2022-c++20/pure2-expected-is-as.cpp.output b/regression-tests/test-results/msvc-2022-c++20/pure2-expected-is-as.cpp.output new file mode 100644 index 000000000..287904b4d --- /dev/null +++ b/regression-tests/test-results/msvc-2022-c++20/pure2-expected-is-as.cpp.output @@ -0,0 +1,67 @@ +pure2-expected-is-as.cpp +pure2-expected-is-as.cpp2(7): error C2039: 'expected': is not a member of 'std' +predefined C++ types (compiler internal)(346): note: see declaration of 'std' +pure2-expected-is-as.cpp2(7): error C2062: type 'int' unexpected +pure2-expected-is-as.cpp2(7): error C2143: syntax error: missing ';' before '{' +pure2-expected-is-as.cpp2(7): error C2143: syntax error: missing ';' before '}' +pure2-expected-is-as.cpp2(8): error C2039: 'expected': is not a member of 'std' +predefined C++ types (compiler internal)(346): note: see declaration of 'std' +pure2-expected-is-as.cpp2(8): error C2062: type 'int' unexpected +pure2-expected-is-as.cpp2(8): error C2143: syntax error: missing ';' before '{' +pure2-expected-is-as.cpp2(8): error C2039: 'unexpected': is not a member of 'std' +predefined C++ types (compiler internal)(346): note: see declaration of 'std' +pure2-expected-is-as.cpp2(8): error C2660: 'unexpected': function does not take 1 arguments +C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.41.34120\include\eh.h(33): note: see declaration of 'unexpected' +pure2-expected-is-as.cpp2(8): note: while trying to match the argument list '(int)' +pure2-expected-is-as.cpp2(8): error C2143: syntax error: missing ';' before '}' +pure2-expected-is-as.cpp2(9): error C2039: 'expected': is not a member of 'std' +predefined C++ types (compiler internal)(346): note: see declaration of 'std' +pure2-expected-is-as.cpp2(9): error C2275: 'std::string': expected an expression instead of a type +pure2-expected-is-as.cpp2(9): error C2065: 'ex3': undeclared identifier +pure2-expected-is-as.cpp2(9): error C2275: 'size_t': expected an expression instead of a type +pure2-expected-is-as.cpp2(11): error C2065: 'ex1': undeclared identifier +pure2-expected-is-as.cpp2(15): error C2065: 'ex1': undeclared identifier +pure2-expected-is-as.cpp2(20): error C2065: 'ex1': undeclared identifier +pure2-expected-is-as.cpp2(25): error C2039: 'unexpected': is not a member of 'std' +predefined C++ types (compiler internal)(346): note: see declaration of 'std' +pure2-expected-is-as.cpp2(25): error C2062: type 'int' unexpected +pure2-expected-is-as.cpp2(25): error C2059: syntax error: '>' +pure2-expected-is-as.cpp2(25): error C2143: syntax error: missing ';' before '{' +pure2-expected-is-as.cpp2(30): error C2065: 'ex1': undeclared identifier +pure2-expected-is-as.cpp2(34): error C2065: 'ex1': undeclared identifier +pure2-expected-is-as.cpp2(39): error C2065: 'ex1': undeclared identifier +pure2-expected-is-as.cpp2(39): error C2119: 'val1': the type for 'auto' cannot be deduced from an empty initializer +pure2-expected-is-as.cpp2(42): error C2065: 'ex2': undeclared identifier +pure2-expected-is-as.cpp2(47): error C2065: 'ex2': undeclared identifier +pure2-expected-is-as.cpp2(52): error C2065: 'ex2': undeclared identifier +pure2-expected-is-as.cpp2(57): error C2039: 'unexpected': is not a member of 'std' +predefined C++ types (compiler internal)(346): note: see declaration of 'std' +pure2-expected-is-as.cpp2(57): error C2062: type 'int' unexpected +pure2-expected-is-as.cpp2(57): error C2059: syntax error: '>' +pure2-expected-is-as.cpp2(57): error C2143: syntax error: missing ';' before '{' +pure2-expected-is-as.cpp2(58): error C2065: 'ex2': undeclared identifier +pure2-expected-is-as.cpp2(61): error C2065: 'ex2': undeclared identifier +pure2-expected-is-as.cpp2(62): error C2065: 'ex2': undeclared identifier +pure2-expected-is-as.cpp2(65): error C2039: 'unexpected': is not a member of 'std' +predefined C++ types (compiler internal)(346): note: see declaration of 'std' +pure2-expected-is-as.cpp2(65): error C2062: type 'int' unexpected +pure2-expected-is-as.cpp2(65): error C2062: type 'unknown-type' unexpected +pure2-expected-is-as.cpp2(65): error C2143: syntax error: missing ';' before '}' +pure2-expected-is-as.cpp2(66): error C2143: syntax error: missing ';' before '<<' +pure2-expected-is-as.cpp2(66): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int +pure2-expected-is-as.cpp2(66): error C2371: 'std::cout': redefinition; different basic types +C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.41.34120\include\iostream(39): note: see declaration of 'std::cout' +pure2-expected-is-as.cpp2(66): error C2059: syntax error: '<<' +pure2-expected-is-as.cpp2(66): error C2143: syntax error: missing ';' before '{' +pure2-expected-is-as.cpp2(66): error C2447: '{': missing function header (old-style formal list?) +pure2-expected-is-as.cpp2(66): error C2059: syntax error: '||' +pure2-expected-is-as.cpp2(66): error C2065: 'Obj': undeclared identifier +pure2-expected-is-as.cpp2(66): error C2065: 'Params': undeclared identifier +pure2-expected-is-as.cpp2(66): error C2059: syntax error: '}' +pure2-expected-is-as.cpp2(66): error C2143: syntax error: missing ')' before ';' +pure2-expected-is-as.cpp2(66): error C2059: syntax error: 'requires' +pure2-expected-is-as.cpp2(66): error C2059: syntax error: ')' +pure2-expected-is-as.cpp2(66): error C2065: 'obj': undeclared identifier +pure2-expected-is-as.cpp2(66): error C3553: decltype expects an expression not a type +pure2-expected-is-as.cpp2(66): error C2065: 'params': undeclared identifier +pure2-expected-is-as.cpp2(66): fatal error C1003: error count exceeds 100; stopping compilation diff --git a/regression-tests/test-results/msvc-2022-c++latest/pure2-expected-is-as.cpp.execution b/regression-tests/test-results/msvc-2022-c++latest/pure2-expected-is-as.cpp.execution new file mode 100644 index 000000000..5483d6cd7 --- /dev/null +++ b/regression-tests/test-results/msvc-2022-c++latest/pure2-expected-is-as.cpp.execution @@ -0,0 +1,15 @@ +ex1 is int +ex1 is 123 +ex1 as int = 123 +ex2 is unexpected and error is: -1 +ex2 is 'empty' aka unexpected and error is: -1 +ex2 as std::unexpected and error = -1 + +expected with value + ...integer 123 + +expected with unexpected + ...unexpected -1 + +expected with value + ...string Expect the unexpected diff --git a/regression-tests/test-results/msvc-2022-c++latest/pure2-expected-is-as.cpp.output b/regression-tests/test-results/msvc-2022-c++latest/pure2-expected-is-as.cpp.output new file mode 100644 index 000000000..6e7839bd6 --- /dev/null +++ b/regression-tests/test-results/msvc-2022-c++latest/pure2-expected-is-as.cpp.output @@ -0,0 +1 @@ +pure2-expected-is-as.cpp diff --git a/regression-tests/test-results/pure2-expected-is-as.cpp b/regression-tests/test-results/pure2-expected-is-as.cpp new file mode 100644 index 000000000..956d9547e --- /dev/null +++ b/regression-tests/test-results/pure2-expected-is-as.cpp @@ -0,0 +1,117 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-expected-is-as.cpp2" + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-expected-is-as.cpp2" +// `std::expected` requires C++23 so a dedicated test file is needed +// since only MSVC supports it at time of writing, and there's no #ifdef +// or `static if` support in Cpp2 (yet?). + +#line 5 "pure2-expected-is-as.cpp2" +[[nodiscard]] auto main() -> int; + +#line 75 "pure2-expected-is-as.cpp2" +auto test_inspect(auto const& x, auto const& msg) -> void; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-expected-is-as.cpp2" + +#line 5 "pure2-expected-is-as.cpp2" +[[nodiscard]] auto main() -> int{ + + std::expected ex1 {123}; + std::expected ex2 {std::unexpected(-1)}; + std::expected ex3 {"Expect the unexpected"}; + + if (cpp2::impl::is(ex1)) { + std::cout << "ex1 is int\n"; + } + + if (cpp2::impl::is(ex1)) { + std::cout << "BUG - ex1 is not a bool\n"; + return -1; + } + + if (cpp2::impl::is(ex1)) { + std::cout << "BUG - ex1 is not 'empty'\n"; + return -1; + } + + if (cpp2::impl::is>(ex1)) { + std::cout << "BUG - ex1 is not unexpected\n"; + return -1; + } + + if (cpp2::impl::is(ex1, 123)) { + std::cout << "ex1 is 123\n"; + } + + if (cpp2::impl::is(ex1, 100)) { + std::cout << "BUG - ex1's value is not 100\n"; + return -1; + } + + auto val1 {cpp2::impl::as_(ex1)}; + std::cout << "ex1 as int = " << cpp2::move(val1) << "\n"; + + if (cpp2::impl::is(ex2)) { + std::cout << "BUG - ex2 is not an int\n"; + return -1; + } + + if (cpp2::impl::is(ex2)) { + std::cout << "BUG - ex2 is not a bool\n"; + return -1; + } + + if (cpp2::impl::is(ex2, 123)) { + std::cout << "BUG - ex2 does not have a value\n"; + return -1; + } + + if (cpp2::impl::is>(ex2)) { + std::cout << "ex2 is unexpected and error is: " << CPP2_UFCS(error)(ex2) << "\n"; + } + + if (cpp2::impl::is(ex2)) { + std::cout << "ex2 is 'empty' aka unexpected and error is: " << CPP2_UFCS(error)(ex2) << "\n"; + } + + auto ex2_err {cpp2::impl::as_>(ex2)}; + std::cout << "ex2 as std::unexpected and error = " << CPP2_UFCS(error)(cpp2::move(ex2_err)) << "\n"; + + test_inspect(cpp2::move(ex1), "expected with value"); + test_inspect(cpp2::move(ex2), "expected with unexpected"); + test_inspect(cpp2::move(ex3), "expected with value"); + + return 0; +} + +#line 75 "pure2-expected-is-as.cpp2" +auto test_inspect(auto const& x, auto const& msg) -> void{ + + auto unwrap {[](cpp2::impl::in> unexp) -> auto{ + return CPP2_UFCS(error)(unexp); + }}; + + std::cout + << "\n" << msg << "\n ..." + << [&] () -> std::string { auto&& _expr = x; + if (cpp2::impl::is(_expr)) { if constexpr( requires{"integer " + std::to_string(cpp2::impl::as(x));} ) if constexpr( std::is_convertible_v(x)))),std::string> ) return "integer " + std::to_string(cpp2::impl::as(x)); else return std::string{}; else return std::string{}; } + else if (cpp2::impl::is>(_expr)) { if constexpr( requires{"unexpected " + std::to_string(cpp2::move(unwrap)(cpp2::impl::as>(x)));} ) if constexpr( std::is_convertible_v " + std::to_string(cpp2::move(unwrap)(cpp2::impl::as>(x))))),std::string> ) return "unexpected " + std::to_string(cpp2::move(unwrap)(cpp2::impl::as>(x))); else return std::string{}; else return std::string{}; } + else if (cpp2::impl::is(_expr)) { if constexpr( requires{"string " + cpp2::impl::as(x);} ) if constexpr( std::is_convertible_v(x))),std::string> ) return "string " + cpp2::impl::as(x); else return std::string{}; else return std::string{}; } + else return " no match"; } + () + << "\n"; +} + diff --git a/regression-tests/test-results/pure2-expected-is-as.cpp2.output b/regression-tests/test-results/pure2-expected-is-as.cpp2.output new file mode 100644 index 000000000..a63d2c2a1 --- /dev/null +++ b/regression-tests/test-results/pure2-expected-is-as.cpp2.output @@ -0,0 +1,2 @@ +pure2-expected-is-as.cpp2... ok (all Cpp2, passes safety checks) +