blob: 5940592488399e44063395fb461928e2f4e6511d [file] [log] [blame]
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +00001// 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 Anforowicz2c1e85c2022-03-17 20:30:50 +000010// most arithmetic or logical operations.
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000011//
Lukasz Anforowicz2c1e85c2022-03-17 20:30:50 +000012// A StrongInt<T> should compile away to a raw T in optimized mode. What this
13// means is that the generated assembly for:
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000014//
Lukasz Anforowicz2c1e85c2022-03-17 20:30:50 +000015// int64 foo = 123;
16// int64 bar = 456;
17// bool is_equal = (foo == bar);
18// constexpr int64 fubar = 789;
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000019//
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 Anforowicz2c1e85c2022-03-17 20:30:50 +000025// bool is_equal = (foo == bar);
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000026// 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 Anforowicze0d6b852022-03-17 15:40:38 +000053// Supported operations:
54// StrongInt<T> = StrongInt<T>
Lukasz Anforowicz2c1e85c2022-03-17 20:30:50 +000055// StrongInt<T> == StrongInt<T>
56// StrongInt<T> < StrongInt<T>
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000057//
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 Anforowicze0d6b852022-03-17 15:40:38 +000060
Marco Polettic61bcc42022-04-08 12:54:30 -070061#ifndef CRUBIT_COMMON_STRONG_INT_H_
62#define CRUBIT_COMMON_STRONG_INT_H_
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000063
64#include <cstdint>
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000065#include <iosfwd>
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000066#include <limits>
67#include <ostream>
68#include <type_traits>
69#include <utility>
70
Dmitri Gribenkoe7f40dc2023-07-10 06:24:07 -070071#include "absl/hash/hash.h"
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000072
Marcel Hlopkof15e8ce2022-04-08 08:46:09 -070073namespace crubit {
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000074
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000075// 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 Anforowicz2c1e85c2022-03-17 20:30:50 +000079template <typename TagType, typename NativeType>
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000080class StrongInt {
81 public:
82 typedef NativeType ValueType;
83
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000084 // Default value initialization.
Lukasz Anforowicz2c1e85c2022-03-17 20:30:50 +000085 constexpr StrongInt() : value_(NativeType()) {}
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000086
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 Anforowicz2c1e85c2022-03-17 20:30:50 +000093 : value_(static_cast<ValueType>(init_value)) {}
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +000094
95 // Use the default copy constructor, assignment, and destructor.
96
97 // Accesses the raw value.
98 constexpr ValueType value() const { return value_; }
99
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +0000100 // 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 Anforowicze0d6b852022-03-17 15:40:38 +0000108 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 Anforowicz2c1e85c2022-03-17 20:30:50 +0000122template <typename TagType, typename ValueType>
123std::ostream &operator<<(std::ostream &os, StrongInt<TagType, ValueType> arg) {
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +0000124 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 Anforowicz2c1e85c2022-03-17 20:30:50 +0000129template <typename TagType>
130std::ostream &operator<<(std::ostream &os, StrongInt<TagType, int8_t> arg) {
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +0000131 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 Anforowicz2c1e85c2022-03-17 20:30:50 +0000136template <typename TagType>
137std::ostream &operator<<(std::ostream &os, StrongInt<TagType, uint8_t> arg) {
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +0000138 return os << static_cast<unsigned int>(arg.value());
139}
140
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +0000141// Define comparison operators. We allow all comparison operators.
Lukasz Anforowicz2c1e85c2022-03-17 20:30:50 +0000142#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 Anforowicze0d6b852022-03-17 15:40:38 +0000147 }
148CRUBIT_STRONG_INT_COMPARISON_OP(==); // NOLINT(whitespace/operators)
149CRUBIT_STRONG_INT_COMPARISON_OP(!=); // NOLINT(whitespace/operators)
150CRUBIT_STRONG_INT_COMPARISON_OP(<); // NOLINT(whitespace/operators)
151CRUBIT_STRONG_INT_COMPARISON_OP(<=); // NOLINT(whitespace/operators)
152CRUBIT_STRONG_INT_COMPARISON_OP(>); // NOLINT(whitespace/operators)
153CRUBIT_STRONG_INT_COMPARISON_OP(>=); // NOLINT(whitespace/operators)
154#undef CRUBIT_STRONG_INT_COMPARISON_OP
155
Marcel Hlopkof15e8ce2022-04-08 08:46:09 -0700156} // namespace crubit
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +0000157
Lukasz Anforowicz2c1e85c2022-03-17 20:30:50 +0000158// Defines the StrongInt using value_type and typedefs it to type_name.
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +0000159// The struct int_type_name ## _tag_ trickery is needed to ensure that a new
160// type is created per type_name.
Marcel Hlopkof15e8ce2022-04-08 08:46:09 -0700161#define CRUBIT_DEFINE_STRONG_INT_TYPE(type_name, value_type) \
162 typedef ::crubit::StrongInt<class type_name##_strong_int_tag_, value_type> \
Lukasz Anforowicze0d6b852022-03-17 15:40:38 +0000163 type_name;
164
Marco Polettic61bcc42022-04-08 12:54:30 -0700165#endif // CRUBIT_COMMON_STRONG_INT_H_