Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc7f1ece authored by Dominik Laskowski's avatar Dominik Laskowski Committed by Android (Google) Code Review
Browse files

Merge changes I931cc58b,Ic907a662

* changes:
  FTL: Downcast to Optional<T> implicitly
  FTL: Add Optional<T>::and_then
parents 1a774262 d48d801b
Loading
Loading
Loading
Loading
+58 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 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 <functional>
#include <optional>

#include <ftl/details/type_traits.h>

namespace android::ftl {

template <typename>
struct Optional;

namespace details {

template <typename>
struct is_optional : std::false_type {};

template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};

template <typename T>
struct is_optional<Optional<T>> : std::true_type {};

template <typename F, typename T>
struct transform_result {
  using type = Optional<std::remove_cv_t<std::invoke_result_t<F, T>>>;
};

template <typename F, typename T>
using transform_result_t = typename transform_result<F, T>::type;

template <typename F, typename T>
struct and_then_result {
  using type = remove_cvref_t<std::invoke_result_t<F, T>>;
  static_assert(is_optional<type>{}, "and_then function must return an optional");
};

template <typename F, typename T>
using and_then_result_t = typename and_then_result<F, T>::type;

}  // namespace details
}  // namespace android::ftl
+27 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 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 <type_traits>

namespace android::ftl::details {

// TODO: Replace with std::remove_cvref_t in C++20.
template <typename U>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;

}  // namespace android::ftl::details
+50 −14
Original line number Diff line number Diff line
@@ -18,9 +18,10 @@

#include <functional>
#include <optional>
#include <type_traits>
#include <utility>

#include <ftl/details/optional.h>

namespace android::ftl {

// Superset of std::optional<T> with monadic operations, as proposed in https://wg21.link/P0798R8.
@@ -31,41 +32,76 @@ template <typename T>
struct Optional final : std::optional<T> {
  using std::optional<T>::optional;

  // Implicit downcast.
  Optional(std::optional<T> other) : std::optional<T>(std::move(other)) {}

  using std::optional<T>::has_value;
  using std::optional<T>::value;

  // Returns Optional<U> where F is a function that maps T to U.
  template <typename F>
  constexpr auto transform(F&& f) const& {
    using U = std::remove_cv_t<std::invoke_result_t<F, decltype(value())>>;
    if (has_value()) return Optional<U>(std::invoke(std::forward<F>(f), value()));
    return Optional<U>();
    using R = details::transform_result_t<F, decltype(value())>;
    if (has_value()) return R(std::invoke(std::forward<F>(f), value()));
    return R();
  }

  template <typename F>
  constexpr auto transform(F&& f) & {
    using U = std::remove_cv_t<std::invoke_result_t<F, decltype(value())>>;
    if (has_value()) return Optional<U>(std::invoke(std::forward<F>(f), value()));
    return Optional<U>();
    using R = details::transform_result_t<F, decltype(value())>;
    if (has_value()) return R(std::invoke(std::forward<F>(f), value()));
    return R();
  }

  template <typename F>
  constexpr auto transform(F&& f) const&& {
    using U = std::invoke_result_t<F, decltype(std::move(value()))>;
    if (has_value()) return Optional<U>(std::invoke(std::forward<F>(f), std::move(value())));
    return Optional<U>();
    using R = details::transform_result_t<F, decltype(std::move(value()))>;
    if (has_value()) return R(std::invoke(std::forward<F>(f), std::move(value())));
    return R();
  }

  template <typename F>
  constexpr auto transform(F&& f) && {
    using U = std::invoke_result_t<F, decltype(std::move(value()))>;
    if (has_value()) return Optional<U>(std::invoke(std::forward<F>(f), std::move(value())));
    return Optional<U>();
    using R = details::transform_result_t<F, decltype(std::move(value()))>;
    if (has_value()) return R(std::invoke(std::forward<F>(f), std::move(value())));
    return R();
  }

  // Returns Optional<U> where F is a function that maps T to Optional<U>.
  template <typename F>
  constexpr auto and_then(F&& f) const& {
    using R = details::and_then_result_t<F, decltype(value())>;
    if (has_value()) return std::invoke(std::forward<F>(f), value());
    return R();
  }

  template <typename F>
  constexpr auto and_then(F&& f) & {
    using R = details::and_then_result_t<F, decltype(value())>;
    if (has_value()) return std::invoke(std::forward<F>(f), value());
    return R();
  }

  template <typename F>
  constexpr auto and_then(F&& f) const&& {
    using R = details::and_then_result_t<F, decltype(std::move(value()))>;
    if (has_value()) return std::invoke(std::forward<F>(f), std::move(value()));
    return R();
  }

  template <typename F>
  constexpr auto and_then(F&& f) && {
    using R = details::and_then_result_t<F, decltype(std::move(value()))>;
    if (has_value()) return std::invoke(std::forward<F>(f), std::move(value()));
    return R();
  }
};

// Deduction guide.
// Deduction guides.
template <typename T>
Optional(T) -> Optional<T>;

template <typename T>
Optional(std::optional<T>) -> Optional<T>;

}  // namespace android::ftl
+3 −6
Original line number Diff line number Diff line
@@ -21,11 +21,12 @@

#include <algorithm>
#include <iterator>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

#include <ftl/details/type_traits.h>

namespace android::ftl {

template <typename>
@@ -80,10 +81,6 @@ class SmallVector final : details::ArrayTraits<T>, details::ArrayComparators<Sma
  using Static = StaticVector<T, N>;
  using Dynamic = SmallVector<T, 0>;

  // TODO: Replace with std::remove_cvref_t in C++20.
  template <typename U>
  using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;

 public:
  FTL_ARRAY_TRAIT(T, value_type);
  FTL_ARRAY_TRAIT(T, size_type);
@@ -104,7 +101,7 @@ class SmallVector final : details::ArrayTraits<T>, details::ArrayComparators<Sma

  // Constructs at most N elements. See StaticVector for underlying constructors.
  template <typename Arg, typename... Args,
            typename = std::enable_if_t<!is_small_vector<remove_cvref_t<Arg>>{}>>
            typename = std::enable_if_t<!is_small_vector<details::remove_cvref_t<Arg>>{}>>
  SmallVector(Arg&& arg, Args&&... args)
      : vector_(std::in_place_type<Static>, std::forward<Arg>(arg), std::forward<Args>(args)...) {}

+82 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include <ftl/unit.h>
#include <gtest/gtest.h>

#include <cstdlib>
#include <functional>
#include <numeric>
#include <utility>
@@ -32,6 +33,29 @@ namespace android::test {
using ftl::Optional;
using ftl::StaticVector;

TEST(Optional, Construct) {
  // Empty.
  EXPECT_EQ(std::nullopt, Optional<int>());
  EXPECT_EQ(std::nullopt, Optional<std::string>(std::nullopt));

  // Value.
  EXPECT_EQ('?', Optional('?'));
  EXPECT_EQ(""s, Optional(std::string()));

  // In place.
  EXPECT_EQ("???"s, Optional<std::string>(std::in_place, 3u, '?'));
  EXPECT_EQ("abc"s, Optional<std::string>(std::in_place, {'a', 'b', 'c'}));

  // Implicit downcast.
  {
    Optional opt = std::optional("test"s);
    static_assert(std::is_same_v<decltype(opt), Optional<std::string>>);

    ASSERT_TRUE(opt);
    EXPECT_EQ(opt.value(), "test"s);
  }
}

TEST(Optional, Transform) {
  // Empty.
  EXPECT_EQ(std::nullopt, Optional<int>().transform([](int) { return 0; }));
@@ -82,4 +106,62 @@ TEST(Optional, Transform) {
                     .transform([](const std::string& s) { return s.length(); }));
}

namespace {

Optional<int> parse_int(const std::string& str) {
  if (const int i = std::atoi(str.c_str())) return i;
  return std::nullopt;
}

}  // namespace

TEST(Optional, AndThen) {
  // Empty.
  EXPECT_EQ(std::nullopt, Optional<int>().and_then([](int) -> Optional<int> { return 0; }));
  EXPECT_EQ(std::nullopt, Optional<int>().and_then([](int) { return Optional<int>(); }));

  // By value.
  EXPECT_EQ(0, Optional(0).and_then([](int x) { return Optional(x); }));
  EXPECT_EQ(123, Optional("123").and_then(parse_int));
  EXPECT_EQ(std::nullopt, Optional("abc").and_then(parse_int));

  // By reference.
  {
    Optional opt = 'x';
    EXPECT_EQ('z', opt.and_then([](char& c) {
      c = 'y';
      return Optional('z');
    }));

    EXPECT_EQ('y', opt);
  }

  // By rvalue reference.
  {
    std::string out;
    EXPECT_EQ("xyz"s, Optional("abc"s).and_then([&out](std::string&& str) {
      out = std::move(str);
      return Optional("xyz"s);
    }));

    EXPECT_EQ(out, "abc"s);
  }

  // Chaining.
  using StringVector = StaticVector<std::string, 3>;
  EXPECT_EQ(14u, Optional(StaticVector{"-"s, "1"s})
                     .and_then([](StringVector&& v) -> Optional<StringVector> {
                       if (v.push_back("4"s)) return v;
                       return {};
                     })
                     .and_then([](const StringVector& v) -> Optional<std::string> {
                       if (v.full()) return std::accumulate(v.begin(), v.end(), std::string());
                       return {};
                     })
                     .and_then(parse_int)
                     .and_then([](int i) {
                       return i > 0 ? std::nullopt : std::make_optional(static_cast<unsigned>(-i));
                     }));
}

}  // namespace android::test