blob: e46160bb778939d3e855f2cb00960f03c5fc96a7 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FBL_STRONG_INT_H_
#define FBL_STRONG_INT_H_
#if !_KERNEL
#include <stddef.h>
#include <functional>
#endif // !_KERNEL
#include <type_traits>
// StrongInt is a strongly-typed wrapper around integer types that supports
// standard arithmetic operations. Different types of StrongInts do not
// implicitly convert, even where the underlying integral type would.
//
// StrongInt provides the standard set of arithmetic operations, including
// basic arithmetic (such as `x + y`), bitwise operations (such as `x | y`),
// comparison operations (such as `x <= y`), and unary operators (such as
// `-x`, `~x`, `++x`, `x++`).
//
// For opaque integer types where arithmetic is not needed (such as handles or
// other identifiers), consider using the HardInt type in <fbl/hard_int.h>
// instead.
//
// StrongInt types are the same size as the underlying type.
//
// Example:
// DEFINE_STRONG_INT(CpuCount, uint64_t);
// DEFINE_STRONG_INT(MemoryBytes, uint64_t);
// ...
// CpuCount c1(3);
// CpuCount c2(5);
// MemoryBytes m(4096);
// c1 + 1; <-- Compile error: can't implicitly convert 1 to CpuCount
// c1 + CpuCount(1); <-- Allowed
// assert(c1 == 3); <-- Compile error: can't implicitly convert 1 to CpuCount
// assert(c1.value() == 3); <-- Allowed
// assert(c1 != c2); <-- Allowed
// c1 = c2; <-- Allowed
// m = c1; <-- Compile error
//
// Non-scalar arithmetic operations (addition, subtraction, etc) are allowed
// on StrongInt types with other instances of the same StrongInt type:
//
// c1 + c2; <-- Allowed, producing the type CpuCount.
// c1 += c2; <-- Allowed
// c1++; <-- Allowed
// c1 + m; <-- Compile error: mixing CpuCount and MemoryBytes
// c1 + 1; <-- Compile error: mixing CpuCount and plain int.
//
// StrongInt types also supports scalar operations (multiplication, division,
// modulo, and shifting by plain integral types) by plain integral types:
//
// CpuCount c(1);
// auto cpu_count = c * 2; <-- Allowed, producing the type `CpuCount`.
// c /= 2; <-- Allowed
// c << 2; <-- Allowed
//
// Bitwise operators are supported on the generated type, though users must
// ensure that these operations are sensible.
//
// The full set of supported arithmetic operations are as follows:
//
// StrongInt + StrongInt --> StrongInt
// StrongInt - StrongInt --> StrongInt
// StrongInt & StrongInt --> StrongInt
// StrongInt | StrongInt --> StrongInt
// StrongInt ^ StrongInt --> StrongInt
//
// StrongInt * Value --> StrongInt
// Value * StrongInt --> StrongInt
//
// StrongInt / Value --> StrongInt
// StrongInt / StrongInt --> Value
//
// StrongInt % StrongInt --> StrongInt
// StrongInt % Value --> StrongInt
//
// StrongInt << Value --> StrongInt
// StrongInt >> Value --> StrongInt
//
// StrongInt { <= | < | == | > | >= | != } StrongInt --> bool
//
// StrongInt++
// StrongInt--
// Create a wrapper around |base_type| named |type_name|.
#define DEFINE_STRONG_INT(type_name, base_type) \
struct type_name##_strong_int_type_tag_ {}; \
using type_name = fbl::StrongInt<type_name##_strong_int_type_tag_, base_type>; \
static_assert(sizeof(type_name) == sizeof(base_type))
namespace fbl {
template <typename UniqueTagType, typename T>
class StrongInt {
public:
constexpr StrongInt() = default;
constexpr explicit StrongInt(T value) : value_(value) {}
constexpr T value() const { return value_; }
// Define comparison operators.
#define FBL_STRONG_INT_COMPARISON_OP(op) \
friend constexpr bool operator op(const StrongInt& lhs, const StrongInt& rhs) { \
return lhs.value_ op rhs.value_; \
}
FBL_STRONG_INT_COMPARISON_OP(==)
FBL_STRONG_INT_COMPARISON_OP(!=)
FBL_STRONG_INT_COMPARISON_OP(<=)
FBL_STRONG_INT_COMPARISON_OP(>=)
FBL_STRONG_INT_COMPARISON_OP(<)
FBL_STRONG_INT_COMPARISON_OP(>)
// Define binary operators where both the LHS and RHS are of the same StrongInt.
#define FBL_STRONG_INT_STRONG_STRONG_BINARY_OP(op) \
friend constexpr StrongInt operator op(const StrongInt& lhs, const StrongInt& rhs) { \
return StrongInt(lhs.value_ op rhs.value_); \
} \
constexpr StrongInt& operator op##=(const StrongInt& other) { \
value_ op## = other.value_; \
return *this; \
}
FBL_STRONG_INT_STRONG_STRONG_BINARY_OP(+)
FBL_STRONG_INT_STRONG_STRONG_BINARY_OP(-)
FBL_STRONG_INT_STRONG_STRONG_BINARY_OP(&)
FBL_STRONG_INT_STRONG_STRONG_BINARY_OP(|)
FBL_STRONG_INT_STRONG_STRONG_BINARY_OP(^)
FBL_STRONG_INT_STRONG_STRONG_BINARY_OP(%)
// Define binary operations which can take place on a compatible numeric type
// on the right hand side.
#define FBL_STRONG_INT_STRONG_NUMERIC_BINARY_OP(op) \
friend constexpr StrongInt operator op(const StrongInt& lhs, const T& rhs) { \
return StrongInt(lhs.value_ op rhs); \
} \
constexpr StrongInt& operator op##=(const T& other) { \
value_ op## = other; \
return *this; \
}
FBL_STRONG_INT_STRONG_NUMERIC_BINARY_OP(/)
FBL_STRONG_INT_STRONG_NUMERIC_BINARY_OP(*)
FBL_STRONG_INT_STRONG_NUMERIC_BINARY_OP(%)
FBL_STRONG_INT_STRONG_NUMERIC_BINARY_OP(<<)
FBL_STRONG_INT_STRONG_NUMERIC_BINARY_OP(>>)
// <value type> * StrongInt. The symmetrical case is implemented in the macro above.
friend constexpr StrongInt operator*(const T& lhs, const StrongInt& rhs) {
return StrongInt(lhs * rhs.value_);
}
// StrongInt / StrongInt == value_type.
friend constexpr T operator/(const StrongInt& lhs, const StrongInt& rhs) {
return lhs.value_ / rhs.value_;
}
// Define unary operations.
constexpr auto& operator++() {
++value_;
return *this;
}
constexpr auto& operator--() {
--value_;
return *this;
}
constexpr auto operator++(int postfix) { return StrongInt(value_++); }
constexpr auto operator--(int postfix) { return StrongInt(value_--); }
constexpr auto operator+() { return StrongInt(+value_); }
constexpr auto operator-() { return StrongInt(-value_); }
constexpr auto operator~() { return StrongInt(~value_); }
// Truth testing.
explicit operator bool() { return value_ != 0; }
private:
static_assert(std::is_integral<T>::value, "T must be an arithmetic type.");
static_assert(!std::is_same<T, bool>::value, "StrongInt does not support bool.");
T value_ = 0;
};
} // namespace fbl
#if !_KERNEL
namespace std {
template <typename UniqueTagType, typename T>
struct hash<fbl::StrongInt<UniqueTagType, T>> {
constexpr size_t operator()(fbl::StrongInt<UniqueTagType, T> strong_int) const noexcept {
return std::hash<T>()(strong_int.value());
}
};
} // namespace std
#endif // !KERNEL
#endif // FBL_STRONG_INT_H_