Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 1 | // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| 2 | // Exceptions. See /LICENSE for license information. |
| 3 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 4 | |
| 5 | // StrongInt<T> is a simple template class mechanism for defining "logical" |
| 6 | // integer-like class types that support almost all of the same functionality |
| 7 | // as native integer types, but which prevents assignment, construction, and |
| 8 | // other operations from other integer-like types. In other words, you cannot |
| 9 | // assign from raw integer types or other StrongInt<> types, nor can you do |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 10 | // most arithmetic or logical operations. |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 11 | // |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 12 | // A StrongInt<T> should compile away to a raw T in optimized mode. What this |
| 13 | // means is that the generated assembly for: |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 14 | // |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 15 | // int64 foo = 123; |
| 16 | // int64 bar = 456; |
| 17 | // bool is_equal = (foo == bar); |
| 18 | // constexpr int64 fubar = 789; |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 19 | // |
| 20 | // ...should be identical to the generated assembly for: |
| 21 | // |
| 22 | // CRUBIT_DEFINE_STRONG_INT_TYPE(MyStrongInt, int64); |
| 23 | // MyStrongInt foo(123); |
| 24 | // MyStrongInt bar(456); |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 25 | // bool is_equal = (foo == bar); |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 26 | // constexpr MyStrongInt fubar(789); |
| 27 | // |
| 28 | // Since the methods are all inline and non-virtual and the class has just |
| 29 | // one data member, the compiler can erase the StrongInt class entirely in its |
| 30 | // code-generation phase. This also means that you can pass StrongInt<T> |
| 31 | // around by value just as you would a raw T. |
| 32 | // |
| 33 | // It is important to note that StrongInt does NOT generate compile time |
| 34 | // warnings or errors for overflows on implicit constant conversions. |
| 35 | // For example, the below demonstrates a case where the 2 are not equivalent |
| 36 | // at compile time and can lead to subtle initialization bugs: |
| 37 | // |
| 38 | // CRUBIT_DEFINE_STRONG_INT_TYPE(MyStrongInt8, int8); |
| 39 | // int8 foo = 1024; // Compile error: const conversion to ... |
| 40 | // MyStrongInt8 foo(1024); // Compiles ok: foo has undefined / 0 value. |
| 41 | // |
| 42 | // Usage: |
| 43 | // CRUBIT_DEFINE_STRONG_INT_TYPE(Name, NativeType); |
| 44 | // |
| 45 | // Defines a new StrongInt type named 'Name' in the current namespace with |
| 46 | // no validation of operations. |
| 47 | // |
| 48 | // Name: The desired name for the new StrongInt typedef. Must be unique |
| 49 | // within the current namespace. |
| 50 | // NativeType: The primitive integral type this StrongInt will hold, as |
| 51 | // defined by std::numeric_limits::is_integer (see <type_traits>). |
| 52 | // |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 53 | // Supported operations: |
| 54 | // StrongInt<T> = StrongInt<T> |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 55 | // StrongInt<T> == StrongInt<T> |
| 56 | // StrongInt<T> < StrongInt<T> |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 57 | // |
| 58 | // This class also provides a .value() accessor method and defines a hash |
| 59 | // functor that allows the IntType to be used as key to hashable containers. |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 60 | |
Marco Poletti | c61bcc4 | 2022-04-08 12:54:30 -0700 | [diff] [blame] | 61 | #ifndef CRUBIT_COMMON_STRONG_INT_H_ |
| 62 | #define CRUBIT_COMMON_STRONG_INT_H_ |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 63 | |
| 64 | #include <cstdint> |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 65 | #include <iosfwd> |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 66 | #include <limits> |
| 67 | #include <ostream> |
| 68 | #include <type_traits> |
| 69 | #include <utility> |
| 70 | |
Dmitri Gribenko | e7f40dc | 2023-07-10 06:24:07 -0700 | [diff] [blame^] | 71 | #include "absl/hash/hash.h" |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 72 | |
Marcel Hlopko | f15e8ce | 2022-04-08 08:46:09 -0700 | [diff] [blame] | 73 | namespace crubit { |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 74 | |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 75 | // Holds an integer value (of type NativeType) and behaves as a NativeType by |
| 76 | // exposing assignment, unary, comparison, and arithmetic operators. |
| 77 | // |
| 78 | // This class is NOT thread-safe. |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 79 | template <typename TagType, typename NativeType> |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 80 | class StrongInt { |
| 81 | public: |
| 82 | typedef NativeType ValueType; |
| 83 | |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 84 | // Default value initialization. |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 85 | constexpr StrongInt() : value_(NativeType()) {} |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 86 | |
| 87 | // Explicit initialization from a numeric primitive. |
| 88 | template < |
| 89 | class T, |
| 90 | class = std::enable_if_t<std::is_same_v< |
| 91 | decltype(static_cast<ValueType>(std::declval<T>())), ValueType>>> |
| 92 | explicit constexpr StrongInt(T init_value) |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 93 | : value_(static_cast<ValueType>(init_value)) {} |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 94 | |
| 95 | // Use the default copy constructor, assignment, and destructor. |
| 96 | |
| 97 | // Accesses the raw value. |
| 98 | constexpr ValueType value() const { return value_; } |
| 99 | |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 100 | // Metadata functions. |
| 101 | static constexpr StrongInt Max() { |
| 102 | return StrongInt(std::numeric_limits<ValueType>::max()); |
| 103 | } |
| 104 | static constexpr StrongInt Min() { |
| 105 | return StrongInt(std::numeric_limits<ValueType>::min()); |
| 106 | } |
| 107 | |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 108 | template <typename H> |
| 109 | friend H AbslHashValue(H h, const StrongInt &i) { |
| 110 | return H::combine(std::move(h), i.value_); |
| 111 | } |
| 112 | |
| 113 | private: |
| 114 | // The integer value of type ValueType. |
| 115 | ValueType value_; |
| 116 | |
| 117 | static_assert(std::numeric_limits<ValueType>::is_integer, |
| 118 | "invalid integer type for strong int"); |
| 119 | }; |
| 120 | |
| 121 | // Provide the << operator, primarily for logging purposes. |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 122 | template <typename TagType, typename ValueType> |
| 123 | std::ostream &operator<<(std::ostream &os, StrongInt<TagType, ValueType> arg) { |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 124 | return os << arg.value(); |
| 125 | } |
| 126 | |
| 127 | // Provide the << operator, primarily for logging purposes. Specialized for int8 |
| 128 | // so that an integer and not a character is printed. |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 129 | template <typename TagType> |
| 130 | std::ostream &operator<<(std::ostream &os, StrongInt<TagType, int8_t> arg) { |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 131 | return os << static_cast<int>(arg.value()); |
| 132 | } |
| 133 | |
| 134 | // Provide the << operator, primarily for logging purposes. Specialized for |
| 135 | // uint8 so that an integer and not a character is printed. |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 136 | template <typename TagType> |
| 137 | std::ostream &operator<<(std::ostream &os, StrongInt<TagType, uint8_t> arg) { |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 138 | return os << static_cast<unsigned int>(arg.value()); |
| 139 | } |
| 140 | |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 141 | // Define comparison operators. We allow all comparison operators. |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 142 | #define CRUBIT_STRONG_INT_COMPARISON_OP(op) \ |
| 143 | template <typename TagType, typename ValueType> \ |
| 144 | constexpr bool operator op(StrongInt<TagType, ValueType> lhs, \ |
| 145 | StrongInt<TagType, ValueType> rhs) { \ |
| 146 | return lhs.value() op rhs.value(); \ |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 147 | } |
| 148 | CRUBIT_STRONG_INT_COMPARISON_OP(==); // NOLINT(whitespace/operators) |
| 149 | CRUBIT_STRONG_INT_COMPARISON_OP(!=); // NOLINT(whitespace/operators) |
| 150 | CRUBIT_STRONG_INT_COMPARISON_OP(<); // NOLINT(whitespace/operators) |
| 151 | CRUBIT_STRONG_INT_COMPARISON_OP(<=); // NOLINT(whitespace/operators) |
| 152 | CRUBIT_STRONG_INT_COMPARISON_OP(>); // NOLINT(whitespace/operators) |
| 153 | CRUBIT_STRONG_INT_COMPARISON_OP(>=); // NOLINT(whitespace/operators) |
| 154 | #undef CRUBIT_STRONG_INT_COMPARISON_OP |
| 155 | |
Marcel Hlopko | f15e8ce | 2022-04-08 08:46:09 -0700 | [diff] [blame] | 156 | } // namespace crubit |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 157 | |
Lukasz Anforowicz | 2c1e85c | 2022-03-17 20:30:50 +0000 | [diff] [blame] | 158 | // Defines the StrongInt using value_type and typedefs it to type_name. |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 159 | // The struct int_type_name ## _tag_ trickery is needed to ensure that a new |
| 160 | // type is created per type_name. |
Marcel Hlopko | f15e8ce | 2022-04-08 08:46:09 -0700 | [diff] [blame] | 161 | #define CRUBIT_DEFINE_STRONG_INT_TYPE(type_name, value_type) \ |
| 162 | typedef ::crubit::StrongInt<class type_name##_strong_int_tag_, value_type> \ |
Lukasz Anforowicz | e0d6b85 | 2022-03-17 15:40:38 +0000 | [diff] [blame] | 163 | type_name; |
| 164 | |
Marco Poletti | c61bcc4 | 2022-04-08 12:54:30 -0700 | [diff] [blame] | 165 | #endif // CRUBIT_COMMON_STRONG_INT_H_ |