FTL: Add mixins for type-safe wrappers
For now, allow mixing in construction, default construction, equality,
ordering, incrementing, and addition.
Bug: 185536303
Test: ftl_test
Change-Id: I9017e124656ba0e7f13f378814c5b1c19a36655b
diff --git a/include/ftl/mixins.h b/include/ftl/mixins.h
new file mode 100644
index 0000000..0e1d200
--- /dev/null
+++ b/include/ftl/mixins.h
@@ -0,0 +1,148 @@
+/*
+ * 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 <ftl/details/mixins.h>
+
+namespace android::ftl {
+
+// CRTP mixins for defining type-safe wrappers that are distinct from their underlying type. Common
+// uses are IDs, opaque handles, and physical quantities. The constructor is provided by (and must
+// be inherited from) the `Constructible` mixin, whereas operators (equality, ordering, arithmetic,
+// etc.) are enabled through inheritance:
+//
+// struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
+// using Constructible::Constructible;
+// };
+//
+// static_assert(!std::is_default_constructible_v<Id>);
+//
+// Unlike `Constructible`, `DefaultConstructible` allows default construction. The default value is
+// zero-initialized unless specified:
+//
+// struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+// ftl::Equatable<Color>,
+// ftl::Orderable<Color> {
+// using DefaultConstructible::DefaultConstructible;
+// };
+//
+// static_assert(Color() == Color(0u));
+// static_assert(ftl::to_underlying(Color(-1)) == 255u);
+// static_assert(Color(1u) < Color(2u));
+//
+// struct Sequence : ftl::DefaultConstructible<Sequence, std::int8_t, -1>,
+// ftl::Equatable<Sequence>,
+// ftl::Orderable<Sequence>,
+// ftl::Incrementable<Sequence> {
+// using DefaultConstructible::DefaultConstructible;
+// };
+//
+// static_assert(Sequence() == Sequence(-1));
+//
+// The underlying type need not be a fundamental type:
+//
+// struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+// ftl::Equatable<Timeout>,
+// ftl::Addable<Timeout> {
+// using DefaultConstructible::DefaultConstructible;
+// };
+//
+// using namespace std::chrono_literals;
+// static_assert(Timeout() + Timeout(5s) == Timeout(15s));
+//
+template <typename Self, typename T>
+struct Constructible {
+ explicit constexpr Constructible(T value) : value_(value) {}
+
+ explicit constexpr operator const T&() const { return value_; }
+
+ private:
+ template <typename, template <typename> class>
+ friend class details::Mixin;
+
+ T value_;
+};
+
+template <typename Self, typename T, auto kDefault = T{}>
+struct DefaultConstructible : Constructible<Self, T> {
+ using Constructible<Self, T>::Constructible;
+ constexpr DefaultConstructible() : DefaultConstructible(T{kDefault}) {}
+};
+
+// Shorthand for casting a type-safe wrapper to its underlying value.
+template <typename Self, typename T>
+constexpr const T& to_underlying(const Constructible<Self, T>& c) {
+ return static_cast<const T&>(c);
+}
+
+// Comparison operators for equality.
+template <typename Self>
+struct Equatable : details::Mixin<Self, Equatable> {
+ constexpr bool operator==(const Self& other) const {
+ return to_underlying(this->self()) == to_underlying(other);
+ }
+
+ constexpr bool operator!=(const Self& other) const { return !(*this == other); }
+};
+
+// Comparison operators for ordering.
+template <typename Self>
+struct Orderable : details::Mixin<Self, Orderable> {
+ constexpr bool operator<(const Self& other) const {
+ return to_underlying(this->self()) < to_underlying(other);
+ }
+
+ constexpr bool operator>(const Self& other) const { return other < this->self(); }
+ constexpr bool operator>=(const Self& other) const { return !(*this < other); }
+ constexpr bool operator<=(const Self& other) const { return !(*this > other); }
+};
+
+// Pre-increment and post-increment operators.
+template <typename Self>
+struct Incrementable : details::Mixin<Self, Incrementable> {
+ constexpr Self& operator++() {
+ ++this->mut();
+ return this->self();
+ }
+
+ constexpr Self operator++(int) {
+ const Self tmp = this->self();
+ operator++();
+ return tmp;
+ }
+};
+
+// Additive operators, including incrementing.
+template <typename Self>
+struct Addable : details::Mixin<Self, Addable>, Incrementable<Self> {
+ constexpr Self& operator+=(const Self& other) {
+ this->mut() += to_underlying(other);
+ return this->self();
+ }
+
+ constexpr Self operator+(const Self& other) const {
+ Self tmp = this->self();
+ return tmp += other;
+ }
+
+ private:
+ using Base = details::Mixin<Self, Addable>;
+ using Base::mut;
+ using Base::self;
+};
+
+} // namespace android::ftl