diff options
Diffstat (limited to 'include/ftl')
| -rw-r--r-- | include/ftl/algorithm.h | 12 | ||||
| -rw-r--r-- | include/ftl/concat.h | 4 | ||||
| -rw-r--r-- | include/ftl/details/hash.h | 123 | ||||
| -rw-r--r-- | include/ftl/expected.h | 78 | ||||
| -rw-r--r-- | include/ftl/function.h | 2 | ||||
| -rw-r--r-- | include/ftl/hash.h | 44 | ||||
| -rw-r--r-- | include/ftl/non_null.h | 112 | ||||
| -rw-r--r-- | include/ftl/optional.h | 10 | ||||
| -rw-r--r-- | include/ftl/unit.h | 18 |
9 files changed, 392 insertions, 11 deletions
diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h index c0f67683ab..68826bb068 100644 --- a/include/ftl/algorithm.h +++ b/include/ftl/algorithm.h @@ -24,6 +24,18 @@ namespace android::ftl { +// Determines if a container contains a value. This is a simplified version of the C++23 +// std::ranges::contains function. +// +// const ftl::StaticVector vector = {1, 2, 3}; +// assert(ftl::contains(vector, 1)); +// +// TODO: Remove in C++23. +template <typename Container, typename Value> +auto contains(const Container& container, const Value& value) -> bool { + return std::find(container.begin(), container.end(), value) != container.end(); +} + // Adapter for std::find_if that converts the return value from iterator to optional. // // const ftl::StaticVector vector = {"upside"sv, "down"sv, "cake"sv}; diff --git a/include/ftl/concat.h b/include/ftl/concat.h index e0774d39f3..7680bed5e8 100644 --- a/include/ftl/concat.h +++ b/include/ftl/concat.h @@ -57,7 +57,7 @@ struct Concat<N, T, Ts...> : Concat<N + details::StaticString<T>::N, Ts...> { template <std::size_t N> struct Concat<N> { static constexpr std::size_t max_size() { return N; } - constexpr std::size_t size() const { return end_ - buffer_; } + constexpr std::size_t size() const { return static_cast<std::size_t>(end_ - buffer_); } constexpr const char* c_str() const { return buffer_; } @@ -68,6 +68,8 @@ struct Concat<N> { protected: constexpr Concat() : end_(buffer_) {} + constexpr Concat(const Concat&) = delete; + constexpr void append() { *end_ = '\0'; } char buffer_[N + 1]; diff --git a/include/ftl/details/hash.h b/include/ftl/details/hash.h new file mode 100644 index 0000000000..230ae51257 --- /dev/null +++ b/include/ftl/details/hash.h @@ -0,0 +1,123 @@ +/* + * 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 <cinttypes> +#include <cstring> + +namespace android::ftl::details { + +// Based on CityHash64 v1.0.1 (http://code.google.com/p/cityhash/), but slightly +// modernized and trimmed for cases with bounded lengths. + +template <typename T = std::uint64_t> +inline T read_unaligned(const void* ptr) { + T v; + std::memcpy(&v, ptr, sizeof(T)); + return v; +} + +template <bool NonZeroShift = false> +constexpr std::uint64_t rotate(std::uint64_t v, std::uint8_t shift) { + if constexpr (!NonZeroShift) { + if (shift == 0) return v; + } + return (v >> shift) | (v << (64 - shift)); +} + +constexpr std::uint64_t shift_mix(std::uint64_t v) { + return v ^ (v >> 47); +} + +__attribute__((no_sanitize("unsigned-integer-overflow"))) +constexpr std::uint64_t hash_length_16(std::uint64_t u, std::uint64_t v) { + constexpr std::uint64_t kPrime = 0x9ddfea08eb382d69ull; + auto a = (u ^ v) * kPrime; + a ^= (a >> 47); + auto b = (v ^ a) * kPrime; + b ^= (b >> 47); + b *= kPrime; + return b; +} + +constexpr std::uint64_t kPrime0 = 0xc3a5c85c97cb3127ull; +constexpr std::uint64_t kPrime1 = 0xb492b66fbe98f273ull; +constexpr std::uint64_t kPrime2 = 0x9ae16a3b2f90404full; +constexpr std::uint64_t kPrime3 = 0xc949d7c7509e6557ull; + +__attribute__((no_sanitize("unsigned-integer-overflow"))) +inline std::uint64_t hash_length_0_to_16(const char* str, std::uint64_t length) { + if (length > 8) { + const auto a = read_unaligned(str); + const auto b = read_unaligned(str + length - 8); + return hash_length_16(a, rotate<true>(b + length, static_cast<std::uint8_t>(length))) ^ b; + } + if (length >= 4) { + const auto a = read_unaligned<std::uint32_t>(str); + const auto b = read_unaligned<std::uint32_t>(str + length - 4); + return hash_length_16(length + (a << 3), b); + } + if (length > 0) { + const auto a = static_cast<unsigned char>(str[0]); + const auto b = static_cast<unsigned char>(str[length >> 1]); + const auto c = static_cast<unsigned char>(str[length - 1]); + const auto y = static_cast<std::uint32_t>(a) + (static_cast<std::uint32_t>(b) << 8); + const auto z = static_cast<std::uint32_t>(length) + (static_cast<std::uint32_t>(c) << 2); + return shift_mix(y * kPrime2 ^ z * kPrime3) * kPrime2; + } + return kPrime2; +} + +__attribute__((no_sanitize("unsigned-integer-overflow"))) +inline std::uint64_t hash_length_17_to_32(const char* str, std::uint64_t length) { + const auto a = read_unaligned(str) * kPrime1; + const auto b = read_unaligned(str + 8); + const auto c = read_unaligned(str + length - 8) * kPrime2; + const auto d = read_unaligned(str + length - 16) * kPrime0; + return hash_length_16(rotate(a - b, 43) + rotate(c, 30) + d, + a + rotate(b ^ kPrime3, 20) - c + length); +} + +__attribute__((no_sanitize("unsigned-integer-overflow"))) +inline std::uint64_t hash_length_33_to_64(const char* str, std::uint64_t length) { + auto z = read_unaligned(str + 24); + auto a = read_unaligned(str) + (length + read_unaligned(str + length - 16)) * kPrime0; + auto b = rotate(a + z, 52); + auto c = rotate(a, 37); + + a += read_unaligned(str + 8); + c += rotate(a, 7); + a += read_unaligned(str + 16); + + const auto vf = a + z; + const auto vs = b + rotate(a, 31) + c; + + a = read_unaligned(str + 16) + read_unaligned(str + length - 32); + z += read_unaligned(str + length - 8); + b = rotate(a + z, 52); + c = rotate(a, 37); + a += read_unaligned(str + length - 24); + c += rotate(a, 7); + a += read_unaligned(str + length - 16); + + const auto wf = a + z; + const auto ws = b + rotate(a, 31) + c; + const auto r = shift_mix((vf + ws) * kPrime2 + (wf + vs) * kPrime0); + return shift_mix(r * kPrime0 + vs) * kPrime2; +} + +} // namespace android::ftl::details diff --git a/include/ftl/expected.h b/include/ftl/expected.h index 12b6102b6f..7e765c5681 100644 --- a/include/ftl/expected.h +++ b/include/ftl/expected.h @@ -18,9 +18,87 @@ #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(); \ + }) + +// Given an expression `expr` that evaluates to an ftl::Expected<T, E> result (R for short), +// FTL_EXPECT unwraps T out of R, or bails out of the enclosing function F if R has an error E. +// While FTL_TRY bails out with R, FTL_EXPECT bails out with E, which is useful when F does not +// need to propagate R because T is not relevant to the caller. +// +// Example usage: +// +// using StringExp = ftl::Expected<std::string, std::errc>; +// +// std::errc repeat(StringExp exp, std::string& out) { +// const std::string str = FTL_EXPECT(exp); +// out = str + str; +// return std::errc::operation_in_progress; +// } +// +// std::string str; +// assert(std::errc::operation_in_progress == repeat(StringExp("ha"s), str)); +// assert("haha"s == str); +// assert(std::errc::bad_message == repeat(ftl::Unexpected(std::errc::bad_message), str)); +// assert("haha"s == str); +// +#define FTL_EXPECT(expr) \ + ({ \ + auto exp_ = (expr); \ + if (!exp_.has_value()) { \ + return std::move(exp_.error()); \ + } \ + exp_.value(); \ + }) + namespace android::ftl { // Superset of base::expected<T, E> with monadic operations. diff --git a/include/ftl/function.h b/include/ftl/function.h index 3538ca4eae..bda5b759bb 100644 --- a/include/ftl/function.h +++ b/include/ftl/function.h @@ -123,7 +123,7 @@ namespace android::ftl { // // Create a typedef to give a more meaningful name and bound the size. // using MyFunction = ftl::Function<int(std::string_view), 2>; // int* ptr = nullptr; -// auto f1 = MyFunction::make_function( +// auto f1 = MyFunction::make( // [cls = &cls, ptr](std::string_view sv) { // return cls->on_string(ptr, sv); // }); diff --git a/include/ftl/hash.h b/include/ftl/hash.h new file mode 100644 index 0000000000..29a6f719ef --- /dev/null +++ b/include/ftl/hash.h @@ -0,0 +1,44 @@ +/* + * 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 <cinttypes> +#include <optional> +#include <string_view> + +#include <ftl/details/hash.h> + +namespace android::ftl { + +// Non-cryptographic hash function (namely CityHash64) for strings with at most 64 characters. +// Unlike std::hash, which returns std::size_t and is only required to produce the same result +// for the same input within a single execution of a program, this hash is stable. +inline std::optional<std::uint64_t> stable_hash(std::string_view view) { + const auto length = view.length(); + if (length <= 16) { + return details::hash_length_0_to_16(view.data(), length); + } + if (length <= 32) { + return details::hash_length_17_to_32(view.data(), length); + } + if (length <= 64) { + return details::hash_length_33_to_64(view.data(), length); + } + return {}; +} + +} // namespace android::ftl diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h index 35d09d71de..4a5d8bffd0 100644 --- a/include/ftl/non_null.h +++ b/include/ftl/non_null.h @@ -68,26 +68,28 @@ class NonNull final { constexpr NonNull(const NonNull&) = default; constexpr NonNull& operator=(const NonNull&) = default; - constexpr const Pointer& get() const { return pointer_; } - constexpr explicit operator const Pointer&() const { return get(); } + [[nodiscard]] constexpr const Pointer& get() const { return pointer_; } + [[nodiscard]] constexpr explicit operator const Pointer&() const { return get(); } // Move operations. These break the invariant, so care must be taken to avoid subsequent access. constexpr NonNull(NonNull&&) = default; constexpr NonNull& operator=(NonNull&&) = default; - constexpr Pointer take() && { return std::move(pointer_); } - constexpr explicit operator Pointer() && { return take(); } + [[nodiscard]] constexpr Pointer take() && { return std::move(pointer_); } + [[nodiscard]] constexpr explicit operator Pointer() && { return take(); } // Dereferencing. - constexpr decltype(auto) operator*() const { return *get(); } - constexpr decltype(auto) operator->() const { return get(); } + [[nodiscard]] constexpr decltype(auto) operator*() const { return *get(); } + [[nodiscard]] constexpr decltype(auto) operator->() const { return get(); } + + [[nodiscard]] constexpr explicit operator bool() const { return !(pointer_ == nullptr); } // Private constructor for ftl::as_non_null. Excluded from candidate constructors for conversions // through the passkey idiom, for clear compilation errors. template <typename P> constexpr NonNull(Passkey, P&& pointer) : pointer_(std::forward<P>(pointer)) { - if (!pointer_) std::abort(); + if (pointer_ == nullptr) std::abort(); } private: @@ -98,11 +100,13 @@ class NonNull final { }; template <typename P> -constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> { +[[nodiscard]] constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> { using Passkey = typename NonNull<std::decay_t<P>>::Passkey; return {Passkey{}, std::forward<P>(pointer)}; } +// NonNull<P> <=> NonNull<Q> + template <typename P, typename Q> constexpr bool operator==(const NonNull<P>& lhs, const NonNull<Q>& rhs) { return lhs.get() == rhs.get(); @@ -113,4 +117,96 @@ constexpr bool operator!=(const NonNull<P>& lhs, const NonNull<Q>& rhs) { return !operator==(lhs, rhs); } +template <typename P, typename Q> +constexpr bool operator<(const NonNull<P>& lhs, const NonNull<Q>& rhs) { + return lhs.get() < rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator<=(const NonNull<P>& lhs, const NonNull<Q>& rhs) { + return lhs.get() <= rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator>=(const NonNull<P>& lhs, const NonNull<Q>& rhs) { + return lhs.get() >= rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator>(const NonNull<P>& lhs, const NonNull<Q>& rhs) { + return lhs.get() > rhs.get(); +} + +// NonNull<P> <=> Q + +template <typename P, typename Q> +constexpr bool operator==(const NonNull<P>& lhs, const Q& rhs) { + return lhs.get() == rhs; +} + +template <typename P, typename Q> +constexpr bool operator!=(const NonNull<P>& lhs, const Q& rhs) { + return lhs.get() != rhs; +} + +template <typename P, typename Q> +constexpr bool operator<(const NonNull<P>& lhs, const Q& rhs) { + return lhs.get() < rhs; +} + +template <typename P, typename Q> +constexpr bool operator<=(const NonNull<P>& lhs, const Q& rhs) { + return lhs.get() <= rhs; +} + +template <typename P, typename Q> +constexpr bool operator>=(const NonNull<P>& lhs, const Q& rhs) { + return lhs.get() >= rhs; +} + +template <typename P, typename Q> +constexpr bool operator>(const NonNull<P>& lhs, const Q& rhs) { + return lhs.get() > rhs; +} + +// P <=> NonNull<Q> + +template <typename P, typename Q> +constexpr bool operator==(const P& lhs, const NonNull<Q>& rhs) { + return lhs == rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator!=(const P& lhs, const NonNull<Q>& rhs) { + return lhs != rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator<(const P& lhs, const NonNull<Q>& rhs) { + return lhs < rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator<=(const P& lhs, const NonNull<Q>& rhs) { + return lhs <= rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator>=(const P& lhs, const NonNull<Q>& rhs) { + return lhs >= rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator>(const P& lhs, const NonNull<Q>& rhs) { + return lhs > rhs.get(); +} + } // namespace android::ftl + +// Specialize std::hash for ftl::NonNull<T> +template <typename P> +struct std::hash<android::ftl::NonNull<P>> { + std::size_t operator()(const android::ftl::NonNull<P>& ptr) const { + return std::hash<P>()(ptr.get()); + } +}; diff --git a/include/ftl/optional.h b/include/ftl/optional.h index 94d8e3d7cb..e245d88c1c 100644 --- a/include/ftl/optional.h +++ b/include/ftl/optional.h @@ -20,13 +20,14 @@ #include <optional> #include <utility> +#include <android-base/expected.h> #include <ftl/details/optional.h> namespace android::ftl { // Superset of std::optional<T> with monadic operations, as proposed in https://wg21.link/P0798R8. // -// TODO: Remove in C++23. +// TODO: Remove standard APIs in C++23. // template <typename T> struct Optional final : std::optional<T> { @@ -109,6 +110,13 @@ struct Optional final : std::optional<T> { return std::forward<F>(f)(); } + // Maps this Optional<T> to expected<T, E> where nullopt becomes E. + template <typename E> + constexpr auto ok_or(E&& e) && -> base::expected<T, E> { + if (has_value()) return std::move(value()); + return base::unexpected(std::forward<E>(e)); + } + // 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 diff --git a/include/ftl/unit.h b/include/ftl/unit.h index e38230b976..62549a3f9d 100644 --- a/include/ftl/unit.h +++ b/include/ftl/unit.h @@ -58,4 +58,22 @@ constexpr auto unit_fn(F&& f) -> UnitFn<std::decay_t<F>> { return {std::forward<F>(f)}; } +namespace details { + +// Identity function for all T except Unit, which maps to void. +template <typename T> +struct UnitToVoid { + template <typename U> + static auto from(U&& value) { + return value; + } +}; + +template <> +struct UnitToVoid<Unit> { + template <typename U> + static void from(U&&) {} +}; + +} // namespace details } // namespace android::ftl |