blob: 5940592488399e44063395fb461928e2f4e6511d [file] [log] [blame] [edit]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// StrongInt<T> is a simple template class mechanism for defining "logical"
// integer-like class types that support almost all of the same functionality
// as native integer types, but which prevents assignment, construction, and
// other operations from other integer-like types. In other words, you cannot
// assign from raw integer types or other StrongInt<> types, nor can you do
// most arithmetic or logical operations.
//
// A StrongInt<T> should compile away to a raw T in optimized mode. What this
// means is that the generated assembly for:
//
// int64 foo = 123;
// int64 bar = 456;
// bool is_equal = (foo == bar);
// constexpr int64 fubar = 789;
//
// ...should be identical to the generated assembly for:
//
// CRUBIT_DEFINE_STRONG_INT_TYPE(MyStrongInt, int64);
// MyStrongInt foo(123);
// MyStrongInt bar(456);
// bool is_equal = (foo == bar);
// constexpr MyStrongInt fubar(789);
//
// Since the methods are all inline and non-virtual and the class has just
// one data member, the compiler can erase the StrongInt class entirely in its
// code-generation phase. This also means that you can pass StrongInt<T>
// around by value just as you would a raw T.
//
// It is important to note that StrongInt does NOT generate compile time
// warnings or errors for overflows on implicit constant conversions.
// For example, the below demonstrates a case where the 2 are not equivalent
// at compile time and can lead to subtle initialization bugs:
//
// CRUBIT_DEFINE_STRONG_INT_TYPE(MyStrongInt8, int8);
// int8 foo = 1024; // Compile error: const conversion to ...
// MyStrongInt8 foo(1024); // Compiles ok: foo has undefined / 0 value.
//
// Usage:
// CRUBIT_DEFINE_STRONG_INT_TYPE(Name, NativeType);
//
// Defines a new StrongInt type named 'Name' in the current namespace with
// no validation of operations.
//
// Name: The desired name for the new StrongInt typedef. Must be unique
// within the current namespace.
// NativeType: The primitive integral type this StrongInt will hold, as
// defined by std::numeric_limits::is_integer (see <type_traits>).
//
// Supported operations:
// StrongInt<T> = StrongInt<T>
// StrongInt<T> == StrongInt<T>
// StrongInt<T> < StrongInt<T>
//
// This class also provides a .value() accessor method and defines a hash
// functor that allows the IntType to be used as key to hashable containers.
#ifndef CRUBIT_COMMON_STRONG_INT_H_
#define CRUBIT_COMMON_STRONG_INT_H_
#include <cstdint>
#include <iosfwd>
#include <limits>
#include <ostream>
#include <type_traits>
#include <utility>
#include "absl/hash/hash.h"
namespace crubit {
// Holds an integer value (of type NativeType) and behaves as a NativeType by
// exposing assignment, unary, comparison, and arithmetic operators.
//
// This class is NOT thread-safe.
template <typename TagType, typename NativeType>
class StrongInt {
public:
typedef NativeType ValueType;
// Default value initialization.
constexpr StrongInt() : value_(NativeType()) {}
// Explicit initialization from a numeric primitive.
template <
class T,
class = std::enable_if_t<std::is_same_v<
decltype(static_cast<ValueType>(std::declval<T>())), ValueType>>>
explicit constexpr StrongInt(T init_value)
: value_(static_cast<ValueType>(init_value)) {}
// Use the default copy constructor, assignment, and destructor.
// Accesses the raw value.
constexpr ValueType value() const { return value_; }
// Metadata functions.
static constexpr StrongInt Max() {
return StrongInt(std::numeric_limits<ValueType>::max());
}
static constexpr StrongInt Min() {
return StrongInt(std::numeric_limits<ValueType>::min());
}
template <typename H>
friend H AbslHashValue(H h, const StrongInt &i) {
return H::combine(std::move(h), i.value_);
}
private:
// The integer value of type ValueType.
ValueType value_;
static_assert(std::numeric_limits<ValueType>::is_integer,
"invalid integer type for strong int");
};
// Provide the << operator, primarily for logging purposes.
template <typename TagType, typename ValueType>
std::ostream &operator<<(std::ostream &os, StrongInt<TagType, ValueType> arg) {
return os << arg.value();
}
// Provide the << operator, primarily for logging purposes. Specialized for int8
// so that an integer and not a character is printed.
template <typename TagType>
std::ostream &operator<<(std::ostream &os, StrongInt<TagType, int8_t> arg) {
return os << static_cast<int>(arg.value());
}
// Provide the << operator, primarily for logging purposes. Specialized for
// uint8 so that an integer and not a character is printed.
template <typename TagType>
std::ostream &operator<<(std::ostream &os, StrongInt<TagType, uint8_t> arg) {
return os << static_cast<unsigned int>(arg.value());
}
// Define comparison operators. We allow all comparison operators.
#define CRUBIT_STRONG_INT_COMPARISON_OP(op) \
template <typename TagType, typename ValueType> \
constexpr bool operator op(StrongInt<TagType, ValueType> lhs, \
StrongInt<TagType, ValueType> rhs) { \
return lhs.value() op rhs.value(); \
}
CRUBIT_STRONG_INT_COMPARISON_OP(==); // NOLINT(whitespace/operators)
CRUBIT_STRONG_INT_COMPARISON_OP(!=); // NOLINT(whitespace/operators)
CRUBIT_STRONG_INT_COMPARISON_OP(<); // NOLINT(whitespace/operators)
CRUBIT_STRONG_INT_COMPARISON_OP(<=); // NOLINT(whitespace/operators)
CRUBIT_STRONG_INT_COMPARISON_OP(>); // NOLINT(whitespace/operators)
CRUBIT_STRONG_INT_COMPARISON_OP(>=); // NOLINT(whitespace/operators)
#undef CRUBIT_STRONG_INT_COMPARISON_OP
} // namespace crubit
// Defines the StrongInt using value_type and typedefs it to type_name.
// The struct int_type_name ## _tag_ trickery is needed to ensure that a new
// type is created per type_name.
#define CRUBIT_DEFINE_STRONG_INT_TYPE(type_name, value_type) \
typedef ::crubit::StrongInt<class type_name##_strong_int_tag_, value_type> \
type_name;
#endif // CRUBIT_COMMON_STRONG_INT_H_