From 6a358d94d5539757840cf57635d6d6908eeb75d4 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Thu, 25 Jun 2026 12:06:38 -0700 Subject: [PATCH] Add StrongTypedef to bn::base Introduce `bn::base::StrongTypedef` that can be used to give a primitive type a distinct identity. The wrapped type is not implicitly convertible to or from its underlying type, but its value be accessed explicitly. The operations available on the new type are opt-in through composable modifiers, such as `Equality`, `Ordered`, `Hashable`, `Arithmetic`, etc. --- base/compiler.h | 13 ++ base/strong_typedef.h | 442 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 455 insertions(+) create mode 100644 base/strong_typedef.h diff --git a/base/compiler.h b/base/compiler.h index a61f59a094..61ed7fe3ec 100644 --- a/base/compiler.h +++ b/base/compiler.h @@ -71,3 +71,16 @@ #define BN_IGNORE_DEPRECATION_WARNINGS_BEGIN BN_IGNORE_WARNINGS_BEGIN("-Wdeprecated-declarations", 4996) #define BN_IGNORE_DEPRECATION_WARNINGS_END BN_IGNORE_WARNINGS_END + +// BN_EMPTY_BASES +// +// Applied to a class definition to enable the empty base optimization across multiple +// empty base classes. Clang and GCC do this unconditionally, but MSVC only collapses a +// single empty base unless the class is annotated with __declspec(empty_bases). Without +// it, a class with several empty bases is padded larger than the sum of its data members. + +#if defined(_MSC_VER) +#define BN_EMPTY_BASES __declspec(empty_bases) +#else +#define BN_EMPTY_BASES +#endif diff --git a/base/strong_typedef.h b/base/strong_typedef.h new file mode 100644 index 0000000000..c642f38bcb --- /dev/null +++ b/base/strong_typedef.h @@ -0,0 +1,442 @@ +// Copyright (c) 2026 Vector 35 Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#pragma once + +#include // IWYU pragma: keep +#include +#include +#include +#include + +#include + +#include "base/compiler.h" + +// StrongTypedef gives a primitive type a distinct identity so that values which +// happen to share an underlying representation (addresses, offsets, indices, +// sizes, ...) cannot be interchanged by accident. +// +// using Address = bn::base::StrongTypedef; +// +// An Address is not implicitly convertible to or from uint64_t. The conversion must be +// spelled explicitly, with static_cast or the Value() accessor. The Tag type is a phantom +// parameter. It need never be a complete type and serves only to distinguish two +// StrongTypedefs over the same underlying type. +// +// Operations are opt-in through modifiers listed after the tag: +// +// using AddressOffset = bn::base::StrongTypedef; +// using Address = bn::base::StrongTypedef, bn::base::Ordered, +// bn::base::Hashable>; +// +// Each modifier is a type with a nested `template struct +// Apply` mixin. StrongTypedef inherits from every modifier's Apply. Each Apply defines +// the operators it adds as hidden friends written in terms of the stored value. +// +// Each Apply has a requires clause naming a Supports... concept that the underlying type +// must satisfy. Applying a modifier to an underlying type that lacks its operations +// therefore fails at the StrongTypedef declaration, with a diagnostic that names the +// unmet concept, instead of at a later use site. +// +// The NonExtractable modifier disables both the explicit operator T() and the Value() +// accessor, so the underlying value can be constructed but never read back out. +// All other modifiers continue to function as normal. + +namespace bn::base { + +template +class StrongTypedef; + +namespace detail { + +// True when Modifier appears in the Mods pack. +template +inline constexpr bool HasModifier = (std::is_same_v || ...); + +// Internal access to a StrongTypedef's underlying value so that modifiers have +// access to it even when NonExtractable is in use. +struct Access +{ + template + static constexpr decltype(auto) Get(Self&& self) noexcept { return (std::forward(self).m_value); } +}; + +// Concepts describing what an underlying type must support for a given modifier. +template concept SupportsAdd = requires(T a, T b) { a + b; a += b; }; +template concept SupportsSubtract = requires(T a, T b) { a - b; a -= b; }; +template concept SupportsMultiply = requires(T a, T b) { a * b; a *= b; }; +template concept SupportsDivide = requires(T a, T b) { a / b; a /= b; }; +template concept SupportsModulo = requires(T a, T b) { a % b; a %= b; }; +template concept SupportsNegate = requires(T a) { -a; }; +template concept SupportsIncrement = requires(T a) { ++a; a++; }; +template concept SupportsDecrement = requires(T a) { --a; a--; }; +template concept SupportsBitAnd = requires(T a, T b) { a & b; a &= b; }; +template concept SupportsBitOr = requires(T a, T b) { a | b; a |= b; }; +template concept SupportsBitXor = requires(T a, T b) { a ^ b; a ^= b; }; +template concept SupportsBitNot = requires(T a) { ~a; }; +template concept SupportsShift = requires(T a) { a << 1; a >> 1; }; +template concept SupportsEquality = requires(T a, T b) { a == b; }; +template concept SupportsOrdering = requires(T a, T b) { a <=> b; }; + +// Operators relating a point type Self to its difference type Diff. +template +struct AffineOps +{ + friend constexpr Diff operator-(const Self& a, const Self& b) { return Diff(detail::Access::Get(a) - detail::Access::Get(b)); } + friend constexpr Self operator+(const Self& a, const Diff& d) { return Self(detail::Access::Get(a) + detail::Access::Get(d)); } + friend constexpr Self operator+(const Diff& d, const Self& a) { return Self(detail::Access::Get(d) + detail::Access::Get(a)); } + friend constexpr Self operator-(const Self& a, const Diff& d) { return Self(detail::Access::Get(a) - detail::Access::Get(d)); } + friend constexpr Self& operator+=(Self& a, const Diff& d) { detail::Access::Get(a) += detail::Access::Get(d); return a; } + friend constexpr Self& operator-=(Self& a, const Diff& d) { detail::Access::Get(a) -= detail::Access::Get(d); return a; } +}; + +} // namespace detail + +// Permits == and !=. The compiler synthesizes != from the defined ==. +struct Equality +{ + template + requires detail::SupportsEquality + struct Apply + { + friend constexpr bool operator==(const Self& a, const Self& b) { return detail::Access::Get(a) == detail::Access::Get(b); } + }; +}; + +// Permits the relational operators via operator<=>. Also includes Equality, because a +// user-provided operator<=> does not by itself make == available. +struct Ordered +{ + template + requires detail::SupportsOrdering + struct Apply : Equality::Apply + { + friend constexpr auto operator<=>(const Self& a, const Self& b) { return detail::Access::Get(a) <=> detail::Access::Get(b); } + }; +}; + +struct Addable +{ + template + requires detail::SupportsAdd + struct Apply + { + friend constexpr Self operator+(const Self& a, const Self& b) { return Self(detail::Access::Get(a) + detail::Access::Get(b)); } + friend constexpr Self& operator+=(Self& a, const Self& b) { detail::Access::Get(a) += detail::Access::Get(b); return a; } + }; +}; + +struct Subtractable +{ + template + requires detail::SupportsSubtract + struct Apply + { + friend constexpr Self operator-(const Self& a, const Self& b) { return Self(detail::Access::Get(a) - detail::Access::Get(b)); } + friend constexpr Self& operator-=(Self& a, const Self& b) { detail::Access::Get(a) -= detail::Access::Get(b); return a; } + }; +}; + +struct Multipliable +{ + template + requires detail::SupportsMultiply + struct Apply + { + friend constexpr Self operator*(const Self& a, const Self& b) { return Self(detail::Access::Get(a) * detail::Access::Get(b)); } + friend constexpr Self& operator*=(Self& a, const Self& b) { detail::Access::Get(a) *= detail::Access::Get(b); return a; } + }; +}; + +struct Divisible +{ + template + requires detail::SupportsDivide + struct Apply + { + friend constexpr Self operator/(const Self& a, const Self& b) { return Self(detail::Access::Get(a) / detail::Access::Get(b)); } + friend constexpr Self& operator/=(Self& a, const Self& b) { detail::Access::Get(a) /= detail::Access::Get(b); return a; } + }; +}; + +struct Modulo +{ + template + requires detail::SupportsModulo + struct Apply + { + friend constexpr Self operator%(const Self& a, const Self& b) { return Self(detail::Access::Get(a) % detail::Access::Get(b)); } + friend constexpr Self& operator%=(Self& a, const Self& b) { detail::Access::Get(a) %= detail::Access::Get(b); return a; } + }; +}; + +struct Negatable +{ + template + requires detail::SupportsNegate + struct Apply + { + friend constexpr Self operator-(const Self& a) { return Self(-detail::Access::Get(a)); } + }; +}; + +struct Incrementable +{ + template + requires detail::SupportsIncrement + struct Apply + { + friend constexpr Self& operator++(Self& a) { ++detail::Access::Get(a); return a; } + friend constexpr Self operator++(Self& a, int) { Self tmp = a; ++detail::Access::Get(a); return tmp; } + }; +}; + +struct Decrementable +{ + template + requires detail::SupportsDecrement + struct Apply + { + friend constexpr Self& operator--(Self& a) { --detail::Access::Get(a); return a; } + friend constexpr Self operator--(Self& a, int) { Self tmp = a; --detail::Access::Get(a); return tmp; } + }; +}; + +// Homogeneous arithmetic (Self op Self -> Self) for group-like types such as an offset +// or a count. Point-like types such as an address should use Affine instead. +struct Arithmetic +{ + template + struct Apply + : Addable::Apply, Subtractable::Apply, Multipliable::Apply, + Divisible::Apply, Modulo::Apply, Negatable::Apply, + Incrementable::Apply, Decrementable::Apply + { + }; +}; + +struct BitAnd +{ + template + requires detail::SupportsBitAnd + struct Apply + { + friend constexpr Self operator&(const Self& a, const Self& b) { return Self(detail::Access::Get(a) & detail::Access::Get(b)); } + friend constexpr Self& operator&=(Self& a, const Self& b) { detail::Access::Get(a) &= detail::Access::Get(b); return a; } + }; +}; + +struct BitOr +{ + template + requires detail::SupportsBitOr + struct Apply + { + friend constexpr Self operator|(const Self& a, const Self& b) { return Self(detail::Access::Get(a) | detail::Access::Get(b)); } + friend constexpr Self& operator|=(Self& a, const Self& b) { detail::Access::Get(a) |= detail::Access::Get(b); return a; } + }; +}; + +struct BitXor +{ + template + requires detail::SupportsBitXor + struct Apply + { + friend constexpr Self operator^(const Self& a, const Self& b) { return Self(detail::Access::Get(a) ^ detail::Access::Get(b)); } + friend constexpr Self& operator^=(Self& a, const Self& b) { detail::Access::Get(a) ^= detail::Access::Get(b); return a; } + }; +}; + +struct BitNot +{ + template + requires detail::SupportsBitNot + struct Apply + { + friend constexpr Self operator~(const Self& a) { return Self(~detail::Access::Get(a)); } + }; +}; + +struct Shiftable +{ + template + requires detail::SupportsShift + struct Apply + { + friend constexpr Self operator<<(const Self& a, int n) { return Self(detail::Access::Get(a) << n); } + friend constexpr Self operator>>(const Self& a, int n) { return Self(detail::Access::Get(a) >> n); } + friend constexpr Self& operator<<=(Self& a, int n) { detail::Access::Get(a) <<= n; return a; } + friend constexpr Self& operator>>=(Self& a, int n) { detail::Access::Get(a) >>= n; return a; } + }; +}; + +struct Bitwise +{ + template + struct Apply + : BitAnd::Apply, BitOr::Apply, BitXor::Apply, + BitNot::Apply, Shiftable::Apply + { + }; +}; + +// Affine relationship with a separate difference type Diff: +// Self - Self -> Diff (the distance between two points) +// Self + Diff -> Self (step a point by a distance) +// Diff + Self -> Self +// Self - Diff -> Self +// Adding two points (Self + Self) is intentionally not provided. +template +struct Affine +{ + template + requires detail::SupportsAdd && detail::SupportsSubtract + struct Apply : detail::AffineOps + { + }; +}; + +namespace detail { + +template +struct SynthOffsetTag {}; + +// The difference type synthesized by AffinePoint. It is a signed group-arithmetic type +// with a tag unique to the point type Self. +template +using SynthOffset = StrongTypedef, SynthOffsetTag, Arithmetic, Ordered>; + +} // namespace detail + +// Like Affine, but synthesizes the difference type and exposes it as Self::Offset, so one +// declaration provides both the affine operators and a named offset type. Requires an +// integral underlying type. +struct AffinePoint +{ + template + requires std::is_integral_v + struct Apply : detail::AffineOps> + { + using Offset = detail::SynthOffset; + }; +}; + +// Makes the type usable as a key in both std and absl hash containers. Enables a +// constrained std::hash specialization and provides an AbslHashValue overload. +struct Hashable +{ + template + struct Apply + { + template + friend H AbslHashValue(H state, const Self& v) + { + return H::combine(std::move(state), detail::Access::Get(v)); + } + }; +}; + +// Marker that enables the StrongTypedef fmt::formatter. Carries no operators. +struct Formattable +{ + template + struct Apply + { + }; +}; + +// Disable both the explicit operator T() and the Value() accessor. +// All other modifiers continue to function as normal. +struct NonExtractable +{ + template + struct Apply + { + }; +}; + +template +class BN_EMPTY_BASES StrongTypedef : public Mods::template Apply, T>... +{ +public: + using UnderlyingType = T; + + constexpr StrongTypedef() = default; + + explicit constexpr StrongTypedef(const T& value) noexcept(std::is_nothrow_copy_constructible_v) + : m_value(value) + { + } + + explicit constexpr StrongTypedef(T&& value) noexcept(std::is_nothrow_move_constructible_v) + : m_value(std::move(value)) + { + } + + explicit constexpr operator T() const noexcept(std::is_nothrow_copy_constructible_v) + requires (!detail::HasModifier) + { + return m_value; + } + + constexpr T& Value() & noexcept + requires (!detail::HasModifier) { return m_value; } + constexpr const T& Value() const& noexcept + requires (!detail::HasModifier) { return m_value; } + +private: + friend struct detail::Access; + + T m_value{}; +}; + +} // namespace bn::base + +namespace std { + +template + requires ::bn::base::detail::HasModifier<::bn::base::Hashable, Mods...> +struct hash<::bn::base::StrongTypedef> +{ + constexpr std::size_t operator()(const ::bn::base::StrongTypedef& v) const + noexcept(noexcept(std::hash {}(::bn::base::detail::Access::Get(v)))) + { + return std::hash {}(::bn::base::detail::Access::Get(v)); + } +}; + +} // namespace std + +// Formatting forwards to the underlying type's formatter, so format specs (e.g. {:#x}) +// pass through. Enabled only when the type carries the Formattable modifier. +template + requires ::bn::base::detail::HasModifier<::bn::base::Formattable, Mods...> +struct fmt::formatter<::bn::base::StrongTypedef> : fmt::formatter +{ + template + auto format(const ::bn::base::StrongTypedef& v, FormatContext& ctx) const + { + return fmt::formatter::format(::bn::base::detail::Access::Get(v), ctx); + } +};