blob: 57448dc1e0d45b3a2117bd8de00d786593d50b59 [file] [log] [blame]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <android-base/expected.h>
#include <ftl/optional.h>
#include <ftl/unit.h>
#include <utility>
// Given an expression `expr` that evaluates to an ftl::Expected<T, E> result (R for short), FTL_TRY
// unwraps T out of R, or bails out of the enclosing function F if R has an error E. The return type
// of F must be R, since FTL_TRY propagates R in the error case. As a special case, ftl::Unit may be
// used as the error E to allow FTL_TRY expressions when F returns `void`.
//
// The non-standard syntax requires `-Wno-gnu-statement-expression-from-macro-expansion` to compile.
// The UnitToVoid conversion allows the macro to be used for early exit from a function that returns
// `void`.
//
// Example usage:
//
// using StringExp = ftl::Expected<std::string, std::errc>;
//
// StringExp repeat(StringExp exp) {
// const std::string str = FTL_TRY(exp);
// return StringExp(str + str);
// }
//
// assert(StringExp("haha"s) == repeat(StringExp("ha"s)));
// assert(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
// return e == std::errc::bad_message;
// }));
//
//
// FTL_TRY may be used in void-returning functions by using ftl::Unit as the error type:
//
// void uppercase(char& c, ftl::Optional<char> opt) {
// c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit())));
// }
//
// char c = '?';
// uppercase(c, std::nullopt);
// assert(c == '?');
//
// uppercase(c, 'a');
// assert(c == 'A');
//
#define FTL_TRY(expr) \
({ \
auto exp_ = (expr); \
if (!exp_.has_value()) { \
using E = decltype(exp_)::error_type; \
return android::ftl::details::UnitToVoid<E>::from(std::move(exp_)); \
} \
exp_.value(); \
})
namespace android::ftl {
// Superset of base::expected<T, E> with monadic operations.
//
// TODO: Extend std::expected<T, E> in C++23.
//
template <typename T, typename E>
struct Expected final : base::expected<T, E> {
using Base = base::expected<T, E>;
using Base::expected;
using Base::error;
using Base::has_value;
using Base::value;
template <typename P>
constexpr bool has_error(P predicate) const {
return !has_value() && predicate(error());
}
constexpr Optional<T> value_opt() const& {
return has_value() ? Optional(value()) : std::nullopt;
}
constexpr Optional<T> value_opt() && {
return has_value() ? Optional(std::move(value())) : std::nullopt;
}
// Delete new for this class. Its base doesn't have a virtual destructor, and
// if it got deleted via base class pointer, it would cause undefined
// behavior. There's not a good reason to allocate this object on the heap
// anyway.
static void* operator new(size_t) = delete;
static void* operator new[](size_t) = delete;
};
template <typename E>
constexpr auto Unexpected(E&& error) {
return base::unexpected(std::forward<E>(error));
}
} // namespace android::ftl