Skip to content

Commit

Permalink
Add is and as support for std::expected
Browse files Browse the repository at this point in the history
  • Loading branch information
bluetarpmedia committed Nov 1, 2024
1 parent 1032a5b commit afc1b10
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 0 deletions.
87 changes: 87 additions & 0 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -2078,6 +2078,93 @@ constexpr auto as( X&& x ) -> decltype(auto) {
}



//-------------------------------------------------------------------------------------------------------------
// std::expected is and as
//
#ifdef __cpp_lib_expected

// is Type
//
template<typename T, typename X>
requires std::is_same_v<X, std::expected<T, typename X::error_type>>
constexpr auto is(X const& x) -> bool
{
return x.has_value();
}

template<typename T, typename U, typename V>
requires std::is_same_v<T, empty>
constexpr auto is(std::expected<U, V> const& x) -> bool
{
return !x.has_value();
}

// is std::unexpected<T> Type
//
template<typename T, typename X>
requires (
std::is_same_v<T, std::unexpected<typename X::error_type>>
&& std::is_same_v<X, std::expected<typename X::value_type, typename X::error_type>>
)
constexpr auto is(X const& x) -> bool
{
return !x.has_value();
}


// is Value
//
template<typename T, typename U>
constexpr auto is(std::expected<T, U> const& x, auto&& value) -> bool
{
// Predicate case
if constexpr (requires{ bool{ value(x) }; }) {
return value(x);
}
else if constexpr (std::is_function_v<decltype(value)> || 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<typename T, typename X>
requires std::is_same_v<X, std::expected<T, typename X::error_type>>
constexpr auto as(X const& x) -> decltype(auto)
{
return x.value();
}

// as std::unexpected<T>
//
template<typename T, typename X>
requires (
std::is_same_v<T, std::unexpected<typename X::error_type>>
&& std::is_same_v<X, std::expected<typename X::value_type, typename X::error_type>>
)
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<typename X::error_type>(x.error());
}
#endif

} // impl


Expand Down
90 changes: 90 additions & 0 deletions regression-tests/pure2-expected-is-as.cpp2
Original file line number Diff line number Diff line change
@@ -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<int, int> = (123);
ex2: std::expected<int, int> = std::unexpected(-1);
ex3: std::expected<std::string, size_t> = ("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<int> {
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<int> {
std::cout << "ex2 is unexpected<int> and error is: " << ex2.error() << "\n";
}

if ex2 is void {
std::cout << "ex2 is 'empty' aka unexpected<int> and error is: " << ex2.error() << "\n";
}

ex2_err:= ex2 as std::unexpected<int>;
std::cout << "ex2 as std::unexpected<int> and error = " << ex2_err.error() << "\n";

test_inspect(ex1, "expected<int, int> with value");
test_inspect(ex2, "expected<int, int> with unexpected");
test_inspect(ex3, "expected<string, size_t> with value");

return 0;
}

test_inspect: ( x: _, msg: _ ) = {

unwrap:= :(unexp: std::unexpected<int>) -> _ = {
return unexp.error();
};

std::cout
<< "\n" << msg << "\n ..."
<< inspect x -> std::string {
is int = "integer " + std::to_string(x as int);
is std::unexpected<int> = "unexpected<int> " + std::to_string(unwrap(x as std::unexpected<int>));
is std::string = "string " + x as std::string;
is _ = " no match";
}
<< "\n";
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ex1 is int
ex1 is 123
ex1 as int = 123
ex2 is unexpected<int> and error is: -1
ex2 is 'empty' aka unexpected<int> and error is: -1
ex2 as std::unexpected<int> and error = -1

expected<int, int> with value
...integer 123

expected<int, int> with unexpected
...unexpected<int> -1

expected<string, size_t> with value
...string Expect the unexpected
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pure2-expected-is-as.cpp
117 changes: 117 additions & 0 deletions regression-tests/test-results/pure2-expected-is-as.cpp
Original file line number Diff line number Diff line change
@@ -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<int,int> ex1 {123};
std::expected<int,int> ex2 {std::unexpected(-1)};
std::expected<std::string,size_t> ex3 {"Expect the unexpected"};

if (cpp2::impl::is<int>(ex1)) {
std::cout << "ex1 is int\n";
}

if (cpp2::impl::is<bool>(ex1)) {
std::cout << "BUG - ex1 is not a bool\n";
return -1;
}

if (cpp2::impl::is<void>(ex1)) {
std::cout << "BUG - ex1 is not 'empty'\n";
return -1;
}

if (cpp2::impl::is<std::unexpected<int>>(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_<int>(ex1)};
std::cout << "ex1 as int = " << cpp2::move(val1) << "\n";

if (cpp2::impl::is<int>(ex2)) {
std::cout << "BUG - ex2 is not an int\n";
return -1;
}

if (cpp2::impl::is<bool>(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<std::unexpected<int>>(ex2)) {
std::cout << "ex2 is unexpected<int> and error is: " << CPP2_UFCS(error)(ex2) << "\n";
}

if (cpp2::impl::is<void>(ex2)) {
std::cout << "ex2 is 'empty' aka unexpected<int> and error is: " << CPP2_UFCS(error)(ex2) << "\n";
}

auto ex2_err {cpp2::impl::as_<std::unexpected<int>>(ex2)};
std::cout << "ex2 as std::unexpected<int> and error = " << CPP2_UFCS(error)(cpp2::move(ex2_err)) << "\n";

test_inspect(cpp2::move(ex1), "expected<int, int> with value");
test_inspect(cpp2::move(ex2), "expected<int, int> with unexpected");
test_inspect(cpp2::move(ex3), "expected<string, size_t> 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<std::unexpected<int>> unexp) -> auto{
return CPP2_UFCS(error)(unexp);
}};

std::cout
<< "\n" << msg << "\n ..."
<< [&] () -> std::string { auto&& _expr = x;
if (cpp2::impl::is<int>(_expr)) { if constexpr( requires{"integer " + std::to_string(cpp2::impl::as<int>(x));} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF(("integer " + std::to_string(cpp2::impl::as<int>(x)))),std::string> ) return "integer " + std::to_string(cpp2::impl::as<int>(x)); else return std::string{}; else return std::string{}; }
else if (cpp2::impl::is<std::unexpected<int>>(_expr)) { if constexpr( requires{"unexpected<int> " + std::to_string(cpp2::move(unwrap)(cpp2::impl::as<std::unexpected<int>>(x)));} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF(("unexpected<int> " + std::to_string(cpp2::move(unwrap)(cpp2::impl::as<std::unexpected<int>>(x))))),std::string> ) return "unexpected<int> " + std::to_string(cpp2::move(unwrap)(cpp2::impl::as<std::unexpected<int>>(x))); else return std::string{}; else return std::string{}; }
else if (cpp2::impl::is<std::string>(_expr)) { if constexpr( requires{"string " + cpp2::impl::as<std::string>(x);} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF(("string " + cpp2::impl::as<std::string>(x))),std::string> ) return "string " + cpp2::impl::as<std::string>(x); else return std::string{}; else return std::string{}; }
else return " no match"; }
()
<< "\n";
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pure2-expected-is-as.cpp2... ok (all Cpp2, passes safety checks)

0 comments on commit afc1b10

Please sign in to comment.