Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add is and as support for std::expected (v2) #971

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 109 additions & 4 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -1185,7 +1185,9 @@ inline constexpr auto is( auto const& x, auto&& value ) -> bool
else if constexpr (requires{ bool{x == value}; }) {
return x == value;
}
return false;
else {
return false;
}
}


Expand Down Expand Up @@ -1302,6 +1304,20 @@ auto as(X const& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto) {
}
}

#ifdef __cpp_lib_expected
// Ensure const-ref `as` for `std::expected` is visible before the template definition below since it won't be found by ADL.
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);

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);
#endif

template< typename C, typename X >
auto as( X& x ) -> decltype(auto) {
if constexpr (std::is_same_v<C, X>) {
Expand Down Expand Up @@ -1566,8 +1582,9 @@ inline constexpr auto is( std::any const& x, auto&& value ) -> bool
auto pvalue = std::any_cast<CPP2_TYPEOF(value)>(&x);
return pvalue && *pvalue == value;
}
// else
return false;
else {
return false;
}
}


Expand Down Expand Up @@ -1613,7 +1630,9 @@ constexpr auto is( std::optional<T> const& x, auto&& value ) -> bool
else if constexpr (requires{ bool{ x.value() == value }; }) {
return x.has_value() && x.value() == value;
}
return false;
else {
return false;
}
}


Expand All @@ -1625,6 +1644,92 @@ constexpr auto as( X const& x ) -> decltype(auto)
{ return x.value(); }


//-------------------------------------------------------------------------------------------------------------
// 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


//-----------------------------------------------------------------------
//
// A variation of GSL's final_action_success / finally
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,72 @@
pure2-expected-is-as.cpp2:7:10: error: no member named 'expected' in namespace 'std'
std::expected<int,int> ex1 {123};
~~~~~^
pure2-expected-is-as.cpp2:7:22: error: expected '(' for function-style cast or type construction
std::expected<int,int> ex1 {123};
~~~^
pure2-expected-is-as.cpp2:8:10: error: no member named 'expected' in namespace 'std'
std::expected<int,int> ex2 {std::unexpected(-1)};
~~~~~^
pure2-expected-is-as.cpp2:8:22: error: expected '(' for function-style cast or type construction
std::expected<int,int> ex2 {std::unexpected(-1)};
~~~^
pure2-expected-is-as.cpp2:9:10: error: no member named 'expected' in namespace 'std'
std::expected<std::string,size_t> ex3 {"Expect the unexpected"};
~~~~~^
pure2-expected-is-as.cpp2:9:30: error: expected '(' for function-style cast or type construction
std::expected<std::string,size_t> ex3 {"Expect the unexpected"};
~~~~~~~~~~~^
In file included from pure2-expected-is-as.cpp:7:
In file included from ../../../include/cpp2util.h:46:
In file included from /Applications/Xcode_14.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/algorithm:1712:
In file included from /Applications/Xcode_14.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/memory:877:
In file included from /Applications/Xcode_14.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iterator:684:
In file included from /Applications/Xcode_14.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__iterator/common_iterator.h:22:
/Applications/Xcode_14.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/variant:1282:3: error: static_assert failed due to requirement '0 < sizeof...(_Types)' "variant must consist of at least one alternative."
static_assert(0 < sizeof...(_Types),
^ ~~~~~~~~~~~~~~~~~~~~~
pure2-expected-is-as.cpp2:11:23: note: in instantiation of template class 'std::variant<>' requested here
if (cpp2::is<int>(ex1)) {
^
pure2-expected-is-as.cpp2:11:23: error: use of undeclared identifier 'ex1'
if (cpp2::is<int>(ex1)) {
^
pure2-expected-is-as.cpp2:15:24: error: use of undeclared identifier 'ex1'
if (cpp2::is<bool>(ex1)) {
^
pure2-expected-is-as.cpp2:20:24: error: use of undeclared identifier 'ex1'
if (cpp2::is<void>(ex1)) {
^
pure2-expected-is-as.cpp2:25:23: error: no member named 'unexpected' in namespace 'std'
if (cpp2::is<std::unexpected<int>>(ex1)) {
~~~~~^
pure2-expected-is-as.cpp2:25:37: error: expected '(' for function-style cast or type construction
if (cpp2::is<std::unexpected<int>>(ex1)) {
~~~^
pure2-expected-is-as.cpp2:25:40: error: use of undeclared identifier 'ex1'; did you mean 'exp'?
if (cpp2::is<std::unexpected<int>>(ex1)) {
^~~
exp
/Applications/Xcode_14.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/math.h:895:1: note: 'exp' declared here
exp(_A1 __lcpp_x) _NOEXCEPT {return ::exp((double)__lcpp_x);}
^
pure2-expected-is-as.cpp2:30:18: error: use of undeclared identifier 'ex1'
if (cpp2::is(ex1, 123)) {
^
pure2-expected-is-as.cpp2:34:18: error: use of undeclared identifier 'ex1'
if (cpp2::is(ex1, 100)) {
^
pure2-expected-is-as.cpp2:39:31: error: use of undeclared identifier 'ex1'
auto val1 {cpp2::as_<int>(ex1)};
^
pure2-expected-is-as.cpp2:42:23: error: use of undeclared identifier 'ex2'
if (cpp2::is<int>(ex2)) {
^
pure2-expected-is-as.cpp2:47:24: error: use of undeclared identifier 'ex2'
if (cpp2::is<bool>(ex2)) {
^
pure2-expected-is-as.cpp2:52:18: error: use of undeclared identifier 'ex2'
if (cpp2::is(ex2, 123)) {
^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.
Loading
Loading