Skip to content

[libc] Add monadic functions to cpp::expected #66523

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
455 changes: 437 additions & 18 deletions libc/src/__support/CPP/expected.h
Original file line number Diff line number Diff line change
@@ -9,43 +9,462 @@
#ifndef LLVM_LIBC_SUPPORT_CPP_EXPECTED_H
#define LLVM_LIBC_SUPPORT_CPP_EXPECTED_H

#include "src/__support/CPP/type_traits.h"
#include "src/__support/CPP/type_traits/always_false.h"
#include "src/__support/CPP/utility.h"

// BEWARE : This implementation is not fully conformant as it doesn't take
// `cpp::reference_wrapper` into account.
// It also doesn't currently implement constructors with 'cpp::in_place_t' and
// 'cpp::unexpect_t'. As a consequence, use of 'cpp::expected<T, E>' where 'T'
// and 'E' are the same type is likely to fail.

namespace __llvm_libc::cpp {

// This is used to hold an unexpected value so that a different constructor is
// selected.
template <class T> class unexpected {
T value;
template <class E> class unexpected {
E err;

public:
constexpr explicit unexpected(T value) : value(value) {}
constexpr T error() { return value; }
constexpr unexpected(const unexpected &) = default;
constexpr unexpected(unexpected &&) = default;

constexpr explicit unexpected(const E &e) : err(e) {}
constexpr explicit unexpected(E &&e) : err(cpp::move(e)) {}

constexpr const E &error() const & { return err; }
constexpr E &error() & { return err; }
constexpr const E &&error() const && { return cpp::move(err); }
constexpr E &&error() && { return cpp::move(err); }
};

template <class E>
constexpr bool operator==(unexpected<E> &x, unexpected<E> &y) {
return x.error() == y.error();
}
template <class E>
constexpr bool operator!=(unexpected<E> &x, unexpected<E> &y) {
return !(x == y);
}

namespace detail {

// Returns exp.value() or cpp::move(exp.value()) if exp is an rvalue reference.
// It also retuns void iff E is an instance of cpp::expected<void, E>.
template <typename E> auto value(E &&exp) {
using value_type = typename cpp::remove_cvref_t<E>::value_type;
if constexpr (cpp::is_void_v<value_type>)
return;
else if constexpr (cpp::is_rvalue_reference_v<decltype(exp)>)
return cpp::move(exp.value());
else
return exp.value();
}

// Returns exp.error() or cpp::move(exp.error()) if exp is an rvalue reference.
template <typename E> auto error(E &&exp) {
if constexpr (cpp::is_rvalue_reference_v<decltype(exp)>)
return cpp::move(exp.error());
else
return exp.error();
}

// Returns the function return type for unary functions.
template <typename T, class F>
struct function_return_type
: cpp::type_identity<cpp::remove_cvref_t<cpp::invoke_result_t<F, T>>> {};

// Returns the function return type for nullary functions.
template <class F>
struct function_return_type<void, F>
: cpp::type_identity<cpp::remove_cvref_t<cpp::invoke_result_t<F>>> {};

// Helper type.
template <typename T, class F>
using function_return_type_t = typename function_return_type<T, F>::type;

// Calls 'f' either directly when E = cpp::expected<void, ...> or with value()
// otherwise.
template <typename E, class F> decltype(auto) invoke(E &&exp, F &&f) {
using T = typename cpp::remove_cvref_t<E>::value_type;
if constexpr (is_void_v<T>)
return cpp::invoke(cpp::forward<F>(f));
else
return cpp::invoke(cpp::forward<F>(f), value(cpp::forward<E>(exp)));
}

// We implement all monadic calls in a single 'apply' function below.
// This enum helps selecting the appropriate logic.
enum class Impl { TRANSFORM, TRANSFORM_ERROR, AND_THEN, OR_ELSE };

// Handles all flavors of 'transform', 'transform_error', 'and_then' and
// 'or_else':
// - 'exp' can be const or non-const.
// - 'exp' can be an 'lvalue_reference' or an 'rvalue_reference'.
// Note: if 'exp' is an 'rvalue_reference' its value / error is moved.
// - 'f' can take zero or one argument.
// - 'f' can return void or another type.
template <Impl impl, typename E, class F> decltype(auto) apply(E &&exp, F &&f) {
using value_type = decltype(value(cpp::forward<E>(exp)));
using UnqualE = cpp::remove_cvref_t<E>;
using T = typename UnqualE::value_type;
if constexpr (impl == Impl::TRANSFORM || impl == Impl::AND_THEN) {
// processing value
using U = function_return_type_t<value_type, F>;
using unexpected_u = typename UnqualE::unexpected_type;
if constexpr (impl == Impl::TRANSFORM) {
static_assert(!cpp::is_reference_v<U>,
"F should return a non-reference type");
using expected_u = typename UnqualE::template rebind<U>;
if (exp.has_value()) {
if constexpr (is_void_v<U>) {
invoke(cpp::forward<E>(exp), cpp::forward<F>(f));
return expected_u();
} else {
return expected_u(invoke(cpp::forward<E>(exp), cpp::forward<F>(f)));
}
} else {
return expected_u(unexpected_u(error(cpp::forward<E>(exp))));
}
}
if constexpr (impl == Impl::AND_THEN) {
if (exp.has_value())
return invoke(cpp::forward<E>(exp), cpp::forward<F>(f));
else
return U(unexpected_u(error(cpp::forward<E>(exp))));
}
} else if constexpr (impl == Impl::OR_ELSE || impl == Impl::TRANSFORM_ERROR) {
// processing error
using error_type = decltype(error(cpp::forward<E>(exp)));
using G = function_return_type_t<error_type, F>;
if constexpr (impl == Impl::TRANSFORM_ERROR) {
static_assert(!cpp::is_reference_v<G>,
"F should return a non-reference type");
using expected_g = typename UnqualE::template rebind_error<G>;
using unexpected_g = typename expected_g::unexpected_type;
if (exp.has_value())
if constexpr (is_void_v<T>)
return expected_g();
else
return expected_g(value(cpp::forward<E>(exp)));
else
return expected_g(unexpected_g(
cpp::invoke(cpp::forward<F>(f), error(cpp::forward<E>(exp)))));
}
if constexpr (impl == Impl::OR_ELSE) {
if (exp.has_value()) {
if constexpr (cpp::is_void_v<T>)
return G();
else
return G(value(cpp::forward<E>(exp)));
} else {
return cpp::invoke(cpp::forward<F>(f), error(cpp::forward<E>(exp)));
}
}
} else {
static_assert(always_false<E>, "");
}
}

template <typename E, class F> decltype(auto) transform(E &&exp, F &&f) {
return apply<Impl::TRANSFORM>(cpp::forward<E>(exp), cpp::forward<F>(f));
}

template <typename E, class F> decltype(auto) transform_error(E &&exp, F &&f) {
return apply<Impl::TRANSFORM_ERROR>(cpp::forward<E>(exp), cpp::forward<F>(f));
}

template <typename E, class F> decltype(auto) and_then(E &&exp, F &&f) {
return apply<Impl::AND_THEN>(cpp::forward<E>(exp), cpp::forward<F>(f));
}

template <typename E, class F> decltype(auto) or_else(E &&exp, F &&f) {
return apply<Impl::OR_ELSE>(cpp::forward<E>(exp), cpp::forward<F>(f));
}

} // namespace detail

template <class T, class E> class expected {
static_assert(cpp::is_trivially_destructible_v<T> &&
cpp::is_trivially_destructible_v<E>,
"prevents dealing with deletion of the union");

union {
T exp;
E unexp;
T val;
E err;
};
bool is_expected;
bool has_val;

public:
using value_type = T;
using error_type = E;
using unexpected_type = cpp::unexpected<E>;
template <class U> using rebind = cpp::expected<U, error_type>;
template <class U> using rebind_error = cpp::expected<value_type, U>;

constexpr expected() : expected(T{}) {}
constexpr expected(T val) : val(val), has_val(true) {}
constexpr expected(const expected &other) { *this = other; }
constexpr expected(expected &&other) { *this = cpp::move(other); }
constexpr expected(const unexpected<E> &err)
: err(err.error()), has_val(false) {}
constexpr expected(unexpected<E> &&err)
: err(cpp::move(err.error())), has_val(false) {}

constexpr expected &operator=(const expected &other) {
if (other.has_value()) {
val = other.value();
has_val = true;
} else {
err = other.error();
has_val = false;
}
return *this;
}
constexpr expected &operator=(expected &&other) {
if (other.has_value()) {
val = cpp::move(other.value());
has_val = true;
} else {
err = cpp::move(other.error());
has_val = false;
}
return *this;
}
constexpr expected &operator=(const unexpected<E> &other) {
has_val = false;
err = other.error();
}
constexpr expected &operator=(unexpected<E> &&other) {
has_val = false;
err = cpp::move(other.error());
}

constexpr bool has_value() const { return has_val; }
constexpr operator bool() const { return has_val; }

constexpr T &value() & { return val; }
constexpr const T &value() const & { return val; }
constexpr T &&value() && { return cpp::move(val); }
constexpr const T &&value() const && { return cpp::move(val); }

constexpr const E &error() const & { return err; }
constexpr E &error() & { return err; }
constexpr const E &&error() const && { return cpp::move(err); }
constexpr E &&error() && { return cpp::move(err); }

constexpr T *operator->() { return &val; }
constexpr const T &operator*() const & { return val; }
constexpr T &operator*() & { return val; }
constexpr const T &&operator*() const && { return cpp::move(val); }
constexpr T &&operator*() && { return cpp::move(val); }

// value_or
template <class U> constexpr T value_or(U &&default_value) const & {
return has_value() ? **this
: static_cast<T>(cpp::forward<U>(default_value));
}
template <class U> constexpr T value_or(U &&default_value) && {
return has_value() ? cpp::move(**this)
: static_cast<T>(cpp::forward<U>(default_value));
}

// transform
template <class F> constexpr decltype(auto) transform(F &&f) & {
return detail::transform(*this, f);
}
template <class F> constexpr decltype(auto) transform(F &&f) const & {
return detail::transform(*this, f);
}
template <class F> constexpr decltype(auto) transform(F &&f) && {
return detail::transform(*this, f);
}
template <class F> constexpr decltype(auto) transform(F &&f) const && {
return detail::transform(*this, f);
}

// transform_error
template <class F> constexpr decltype(auto) transform_error(F &&f) & {
return detail::transform_error(*this, f);
}
template <class F> constexpr decltype(auto) transform_error(F &&f) const & {
return detail::transform_error(*this, f);
}
template <class F> constexpr decltype(auto) transform_error(F &&f) && {
return detail::transform_error(*this, f);
}
template <class F> constexpr decltype(auto) transform_error(F &&f) const && {
return detail::transform_error(*this, f);
}

// and_then
template <class F> constexpr decltype(auto) and_then(F &&f) & {
return detail::and_then(*this, f);
}
template <class F> constexpr decltype(auto) and_then(F &&f) const & {
return detail::and_then(*this, f);
}
template <class F> constexpr decltype(auto) and_then(F &&f) && {
return detail::and_then(*this, f);
}
template <class F> constexpr decltype(auto) and_then(F &&f) const && {
return detail::and_then(*this, f);
}

// or_else
template <class F> constexpr decltype(auto) or_else(F &&f) & {
return detail::or_else(*this, f);
}
template <class F> constexpr decltype(auto) or_else(F &&f) const & {
return detail::or_else(*this, f);
}
template <class F> constexpr decltype(auto) or_else(F &&f) && {
return detail::or_else(*this, f);
}
template <class F> constexpr decltype(auto) or_else(F &&f) const && {
return detail::or_else(*this, f);
}
};

template <class E> class expected<void, E> {
static_assert(cpp::is_trivially_destructible_v<E>);

E err;
bool has_val;

public:
constexpr expected(T exp) : exp(exp), is_expected(true) {}
constexpr expected(unexpected<E> unexp)
: unexp(unexp.error()), is_expected(false) {}
using value_type = void;
using error_type = E;
using unexpected_type = cpp::unexpected<E>;
template <class U> using rebind = cpp::expected<U, error_type>;
template <class U> using rebind_error = cpp::expected<value_type, U>;

constexpr expected() : has_val(true) {}
constexpr expected(const expected &other) { *this = other; }
constexpr expected(expected &&other) { *this = cpp::move(other); }
constexpr expected(const unexpected<E> &err)
: err(err.error()), has_val(false) {}
constexpr expected(unexpected<E> &&err)
: err(cpp::move(err.error())), has_val(false) {}

constexpr expected &operator=(const expected &other) {
if (other.has_value()) {
has_val = true;
} else {
err = other.error();
has_val = false;
}
return *this;
}
constexpr expected &operator=(expected &&other) {
if (other.has_value()) {
has_val = true;
} else {
err = cpp::move(other.error());
has_val = false;
}
return *this;
}
constexpr expected &operator=(const unexpected<E> &other) {
has_val = false;
err = other.error();
}
constexpr expected &operator=(unexpected<E> &&other) {
has_val = false;
err = cpp::move(other.error());
}

constexpr bool has_value() const { return has_val; }
constexpr operator bool() const { return has_val; }

constexpr bool has_value() { return is_expected; }
constexpr void value() const & {}
constexpr void value() && {}

constexpr T value() { return exp; }
constexpr E error() { return unexp; }
constexpr const E &error() const & { return err; }
constexpr E &error() & { return err; }
constexpr const E &&error() const && { return cpp::move(err); }
constexpr E &&error() && { return cpp::move(err); }

constexpr operator bool() { return is_expected; }
constexpr void operator*() const {}

constexpr T &operator*() { return exp; }
constexpr const T &operator*() const { return exp; }
constexpr T *operator->() { return &exp; }
constexpr const T *operator->() const { return &exp; }
// transform
template <class F> constexpr decltype(auto) transform(F &&f) & {
return detail::transform(*this, f);
}
template <class F> constexpr decltype(auto) transform(F &&f) const & {
return detail::transform(*this, f);
}
template <class F> constexpr decltype(auto) transform(F &&f) && {
return detail::transform(*this, f);
}
template <class F> constexpr decltype(auto) transform(F &&f) const && {
return detail::transform(*this, f);
}

// transform_error
template <class F> constexpr decltype(auto) transform_error(F &&f) & {
return detail::transform_error(*this, f);
}
template <class F> constexpr decltype(auto) transform_error(F &&f) const & {
return detail::transform_error(*this, f);
}
template <class F> constexpr decltype(auto) transform_error(F &&f) && {
return detail::transform_error(*this, f);
}
template <class F> constexpr decltype(auto) transform_error(F &&f) const && {
return detail::transform_error(*this, f);
}

// and_then
template <class F> constexpr decltype(auto) and_then(F &&f) & {
return detail::and_then(*this, f);
}
template <class F> constexpr decltype(auto) and_then(F &&f) const & {
return detail::and_then(*this, f);
}
template <class F> constexpr decltype(auto) and_then(F &&f) && {
return detail::and_then(*this, f);
}
template <class F> constexpr decltype(auto) and_then(F &&f) const && {
return detail::and_then(*this, f);
}

// or_else
template <class F> constexpr decltype(auto) or_else(F &&f) & {
return detail::or_else(*this, f);
}
template <class F> constexpr decltype(auto) or_else(F &&f) const & {
return detail::or_else(*this, f);
}
template <class F> constexpr decltype(auto) or_else(F &&f) && {
return detail::or_else(*this, f);
}
template <class F> constexpr decltype(auto) or_else(F &&f) const && {
return detail::or_else(*this, f);
}
};

template <class T, class E>
constexpr bool operator==(const expected<T, E> &lhs,
const expected<T, E> &rhs) {
if (lhs.has_value() != rhs.has_value())
return false;
if (lhs.has_value()) {
if constexpr (cpp::is_void_v<T>)
return true;
else
return *lhs == *rhs;
}
return lhs.error() == rhs.error();
}

template <class T, class E>
constexpr bool operator!=(const expected<T, E> &lhs,
const expected<T, E> &rhs) {
return !(lhs == rhs);
}

} // namespace __llvm_libc::cpp

#endif // LLVM_LIBC_SUPPORT_CPP_EXPECTED_H
10 changes: 10 additions & 0 deletions libc/test/src/__support/CPP/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -86,6 +86,16 @@ add_libc_test(
libc.src.__support.CPP.optional
)

add_libc_test(
expected_test
SUITE
libc-cpp-utils-tests
SRCS
expected_test.cpp
DEPENDS
libc.src.__support.CPP.expected
)

add_libc_test(
span_test
SUITE
272 changes: 272 additions & 0 deletions libc/test/src/__support/CPP/expected_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
//===-- Unittests for Expected --------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/CPP/expected.h"
#include "test/UnitTest/Test.h"

using __llvm_libc::cpp::expected;
using __llvm_libc::cpp::unexpected;

enum class error { _ = 0, B };
enum class fatal_error { _ = 0, D };

auto make_error() { return unexpected<error>{error::B}; }

TEST(LlvmLibcExpectedTest, DefaultCtor) {
{
expected<int, error> init;
EXPECT_TRUE(init.has_value());
EXPECT_TRUE((bool)init);
EXPECT_EQ(init.value(), int{});
EXPECT_EQ(*init, int{});
}
{
expected<void, error> init;
EXPECT_TRUE(init.has_value());
EXPECT_TRUE((bool)init);
}
}

TEST(LlvmLibcExpectedTest, ValueCtor) {
expected<int, error> init(1);
EXPECT_TRUE(init.has_value());
EXPECT_TRUE((bool)init);
EXPECT_EQ(init.value(), 1);
EXPECT_EQ(*init, 1);
}

TEST(LlvmLibcExpectedTest, ErrorCtor) {
{
expected<int, error> init = make_error();
EXPECT_FALSE(init.has_value());
EXPECT_FALSE((bool)init);
EXPECT_EQ(init.error(), error::B);
}
{
expected<void, error> init = make_error();
EXPECT_FALSE(init.has_value());
EXPECT_FALSE((bool)init);
EXPECT_EQ(init.error(), error::B);
}
}

// value_or
TEST(LlvmLibcExpectedTest, ValueOr_ReuseValue) {
expected<int, error> init(1);
EXPECT_EQ(init.value_or(2), int(1));
}

TEST(LlvmLibcExpectedTest, ValueOr_UseProvided) {
expected<int, error> init = make_error();
EXPECT_EQ(init.value_or(2), int(2));
}

// transform

TEST(LlvmLibcExpectedTest, Transform_HasValue) {
{ // int to int
expected<int, error> init(1);
expected<int, error> expected_next(2);
expected<int, error> actual_next = init.transform([](int) { return 2; });
EXPECT_TRUE(actual_next == expected_next);
}
{ // void to int
expected<void, error> init;
expected<int, error> expected_next(2);
expected<int, error> actual_next = init.transform([]() { return 2; });
EXPECT_TRUE(actual_next == expected_next);
}
{ // int to void
expected<int, error> init(1);
expected<void, error> expected_next;
expected<void, error> actual_next = init.transform([](int) {});
EXPECT_TRUE(actual_next == expected_next);
}
{ // void to void
expected<void, error> init;
expected<void, error> actual_next = init.transform([]() {});
EXPECT_TRUE(actual_next == init);
}
}

TEST(LlvmLibcExpectedTest, Transform_HasError) {
{ // int to int
expected<int, error> init = make_error();
expected<int, error> expected_next = make_error();
expected<int, error> actual_next = init.transform([](int) { return 2; });
EXPECT_TRUE(actual_next == expected_next);
}
{ // void to int
expected<void, error> init = make_error();
expected<int, error> expected_next = make_error();
expected<int, error> actual_next = init.transform([]() { return 2; });
EXPECT_TRUE(actual_next == expected_next);
}
{ // int to void
expected<int, error> init = make_error();
expected<void, error> expected_next = make_error();
expected<void, error> actual_next = init.transform([](int) {});
EXPECT_TRUE(actual_next == expected_next);
}
{ // void to void
expected<void, error> init = make_error();
expected<void, error> actual_next = init.transform([]() {});
EXPECT_TRUE(actual_next == init);
}
}

// transform_error

TEST(LlvmLibcExpectedTest, TransformError_HasValue) {
{ // int
expected<int, error> init(1);
expected<int, fatal_error> actual_next =
init.transform_error([](error) { return fatal_error(); });
ASSERT_TRUE(actual_next.has_value());
EXPECT_TRUE(*actual_next == *init);
}
{ // void
expected<void, error> init;
expected<void, fatal_error> actual_next =
init.transform_error([](error) { return fatal_error(); });
ASSERT_TRUE(actual_next.has_value());
}
}

TEST(LlvmLibcExpectedTest, TransformError_HasError) {
{ // int
expected<int, error> init = make_error();
expected<int, fatal_error> actual_next =
init.transform_error([&](error) { return fatal_error::D; });
ASSERT_TRUE(!actual_next.has_value());
ASSERT_EQ(actual_next.error(), fatal_error::D);
}
{ // void
expected<void, error> init = make_error();
expected<void, fatal_error> actual_next =
init.transform_error([&](error) { return fatal_error::D; });
ASSERT_TRUE(!actual_next.has_value());
ASSERT_EQ(actual_next.error(), fatal_error::D);
}
}

// and_then

TEST(LlvmLibcExpectedTest, AndThen_HasValue) {
{ // int to int
expected<int, error> init(1);
expected<int, error> expected_next(2);
expected<int, error> actual_next =
init.and_then([&](int) { return expected_next; });
EXPECT_TRUE(actual_next == expected_next);
}
{ // void to int
expected<void, error> init;
expected<int, error> expected_next(2);
expected<int, error> actual_next =
init.and_then([&]() { return expected_next; });
EXPECT_TRUE(actual_next == expected_next);
}
{ // int to void
expected<int, error> init(1);
expected<void, error> expected_next;
expected<void, error> actual_next =
init.and_then([&](int) { return expected_next; });
EXPECT_TRUE(actual_next == expected_next);
}
{ // void to void
expected<void, error> init;
expected<void, error> actual_next = init.and_then([&]() { return init; });
EXPECT_TRUE(actual_next == init);
}
}

TEST(LlvmLibcExpectedTest, AndThen_HasError) {
{ // int to int
expected<int, error> init = make_error();
expected<int, error> expected_next = make_error();
expected<int, error> actual_next =
init.and_then([&](int) { return expected<int, error>(1); });
EXPECT_TRUE(actual_next == expected_next);
}
{ // void to int
expected<void, error> init = make_error();
expected<int, error> expected_next = make_error();
expected<int, error> actual_next =
init.and_then([&]() { return expected<int, error>(1); });
EXPECT_TRUE(actual_next == expected_next);
}
{ // int to void
expected<int, error> init = make_error();
expected<void, error> expected_next = make_error();
expected<void, error> actual_next =
init.and_then([&](int) { return expected<void, error>(); });
EXPECT_TRUE(actual_next == expected_next);
}
{ // void to void
expected<void, error> init = make_error();
expected<void, error> expected_next = make_error();
expected<void, error> actual_next =
init.and_then([&]() { return expected<void, error>(); });
EXPECT_TRUE(actual_next == expected_next);
}
}

// or_else

TEST(LlvmLibcExpectedTest, OrElse_HasValue) {
{ // int
using T = expected<int, error>;
bool called = false;
T init(1);
T actual_next = init.or_else([&](error) {
called = true;
return T();
});
EXPECT_TRUE(actual_next == init);
EXPECT_FALSE(called);
}
{ // void
using T = expected<void, error>;
bool called = false;
T init;
T actual_next = init.or_else([&](error) {
called = true;
return T();
});
EXPECT_TRUE(actual_next == init);
EXPECT_FALSE(called);
}
}

TEST(LlvmLibcExpectedTest, OrElse_HasError) {
{ // int
bool called = false;
expected<int, error> init = make_error();
expected<int, error> expected_next(10);
expected<int, error> actual_next = init.or_else([&](error) {
called = true;
return expected_next;
});
EXPECT_TRUE(actual_next == expected_next);
EXPECT_TRUE(called);
}
{ // void
bool called = false;
expected<void, error> init = make_error();
expected<void, error> expected_next;
expected<void, error> actual_next = init.or_else([&](error) {
called = true;
return expected_next;
});
EXPECT_TRUE(actual_next == expected_next);
EXPECT_TRUE(called);
}
}

// transform_error
2 changes: 2 additions & 0 deletions utils/bazel/llvm-project-overlay/libc/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -199,6 +199,8 @@ libc_support_library(
name = "__support_cpp_expected",
hdrs = ["src/__support/CPP/expected.h"],
deps = [
"__support_cpp_type_traits",
"__support_cpp_utility",
":libc_root",
],
)
Original file line number Diff line number Diff line change
@@ -38,6 +38,16 @@ cc_test(
],
)

cc_test(
name = "expected_test",
srcs = ["expected_test.cpp"],
deps = [
"//libc:__support_cpp_expected",
"//libc:libc_root",
"//libc/test/UnitTest:LibcUnitTest",
],
)

cc_test(
name = "integer_sequence_test",
srcs = ["integer_sequence_test.cpp"],