commons 0.1.5
Header-only C++23 library of common/shared types for the C++ libraries
Loading...
Searching...
No Matches
id.hpp
Go to the documentation of this file.
1#pragma once
2
29
30#include <commons/config.hpp>
31#include <commons/types.hpp>
32
33#include <concepts>
34#include <cstddef>
35#include <cstdint>
36#include <format>
37#include <functional>
38#include <ostream>
39#include <string>
40#include <string_view>
41#include <type_traits>
42#include <utility>
43
44#if COMMONS_WITH_ULID
45#include <ulid/ulid.h>
46#endif
47
48namespace comms {
49
50// -- repr gating -------------------------------------------------------------
51
52// The trait names follow the STL's `std::is_*` lower_case convention so they
53// read naturally alongside the standard type traits — overriding the project's
54// CamelCase rule for these specific predicates.
55// NOLINTBEGIN(readability-identifier-naming)
56
59template <class T>
60struct is_allowed_id_repr : std::false_type {};
61
62template <>
63struct is_allowed_id_repr<std::uint8_t> : std::true_type {};
64template <>
65struct is_allowed_id_repr<std::uint16_t> : std::true_type {};
66template <>
67struct is_allowed_id_repr<std::uint32_t> : std::true_type {};
68template <>
69struct is_allowed_id_repr<std::uint64_t> : std::true_type {};
70template <>
71struct is_allowed_id_repr<std::string> : std::true_type {};
72
73#if COMMONS_WITH_ULID
74template <>
75struct is_allowed_id_repr<::ulid::Ulid> : std::true_type {};
76#endif
77
78// NOLINTEND(readability-identifier-naming)
79
81template <class T>
82concept AllowedIdRepr = is_allowed_id_repr<std::remove_cvref_t<T>>::value;
83
86template <class Tag>
87concept NamedIdTag = requires {
88 { Tag::name } -> std::convertible_to<std::string_view>;
89};
90
91namespace detail {
92
93template <class T>
94concept StdToStringable = requires(const T& v) {
95 { std::to_string(v) } -> std::convertible_to<std::string>;
96};
97
98} // namespace detail
99
100// -- the type ----------------------------------------------------------------
101
105template <class Tag, AllowedIdRepr Repr>
106class Id {
107public:
108 using tag_type = Tag;
109 using repr_type = Repr;
110
116 requires(!std::default_initializable<Repr>)
117 = delete;
118 Id()
119 requires std::default_initializable<Repr>
120 = default;
121
124 template <class U>
125 requires std::constructible_from<Repr, U&&> && (!std::is_same_v<std::remove_cvref_t<U>, Id>)
126 explicit Id(U&& u) : value_(std::forward<U>(u)) {}
127
129 explicit Id(std::string_view sv)
130 requires std::is_same_v<Repr, std::string>
131 : value_(sv) {}
132
133 [[nodiscard]] const Repr& value() const& noexcept {
134 return value_;
135 }
136 [[nodiscard]] Repr& value() & noexcept {
137 return value_;
138 }
139 [[nodiscard]] Repr&& value() && noexcept {
140 return std::move(value_);
141 }
142
143 [[nodiscard]] auto operator<=>(const Id&) const = default;
144 [[nodiscard]] bool operator==(const Id&) const = default;
145
146private:
147 Repr value_{};
148};
149
150// -- aliases -----------------------------------------------------------------
151
152template <class Tag>
153using Uint8Id = Id<Tag, std::uint8_t>;
154template <class Tag>
155using Uint16Id = Id<Tag, std::uint16_t>;
156template <class Tag>
157using Uint32Id = Id<Tag, std::uint32_t>;
158template <class Tag>
159using Uint64Id = Id<Tag, std::uint64_t>;
160
161template <class Tag>
162using StringId = Id<Tag, std::string>;
163
164#if COMMONS_WITH_ULID
165template <class Tag>
166using UlidId = Id<Tag, ::ulid::Ulid>;
167#endif
168
169// -- reflection --------------------------------------------------------------
170// As with `is_allowed_id_repr`, these traits keep the STL `std::is_*` spelling.
171// NOLINTBEGIN(readability-identifier-naming)
172
173template <class T>
174struct is_id : std::false_type {};
175
176template <class Tag, class Repr>
177struct is_id<Id<Tag, Repr>> : std::true_type {};
178
179template <class T>
180inline constexpr bool is_id_v = is_id<std::remove_cvref_t<T>>::value;
181
182// NOLINTEND(readability-identifier-naming)
183
184template <class T>
185concept IdType = is_id_v<T>;
186
187// -- free helpers ------------------------------------------------------------
188
190template <class Tag, class Repr>
191[[nodiscard]] const Repr& to_underlying(const Id<Tag, Repr>& id) noexcept {
192 return id.value();
193}
194
196template <class Tag, class Repr>
197 requires std::is_same_v<Repr, std::string>
198[[nodiscard]] std::string to_string(const Id<Tag, Repr>& id) {
199 return id.value();
200}
201
203template <class Tag, class Repr>
204 requires(!std::is_same_v<Repr, std::string>) && detail::StdToStringable<Repr>
205[[nodiscard]] std::string to_string(const Id<Tag, Repr>& id) {
206 return std::to_string(id.value());
207}
208
209#if COMMONS_WITH_ULID
211template <class Tag, class Repr>
212 requires std::is_same_v<Repr, ::ulid::Ulid>
213[[nodiscard]] std::string to_string(const Id<Tag, Repr>& id) {
214 return id.value().string();
215}
216#endif
217
220template <class Tag, class Repr>
221[[nodiscard]] std::string display_string(const Id<Tag, Repr>& id) {
222 if constexpr (NamedIdTag<Tag>) {
223 std::string out{std::string_view{Tag::name}};
224 out.push_back('/');
225 out += to_string(id);
226 return out;
227 } else {
228 return to_string(id);
229 }
230}
231
232template <class Tag, class Repr>
233std::ostream& operator<<(std::ostream& os, const Id<Tag, Repr>& id) {
234 return os << to_string(id);
235}
236
237} // namespace comms
238
239// -- std::hash + std::formatter ---------------------------------------------
240// Specializations live in namespace std (the primary templates are visible),
241// matching the layout used by semver.hpp and the rest of commons.
242
246template <class Tag, class Repr>
247 requires requires(const Repr& r) {
248 { std::hash<Repr>{}(r) } -> std::convertible_to<std::size_t>;
249 }
250struct std::hash<comms::Id<Tag, Repr>> {
251 [[nodiscard]] std::size_t operator()(const comms::Id<Tag, Repr>& id) const noexcept {
252 return std::hash<Repr>{}(id.value());
253 }
254};
255
259template <class Tag, class Repr, class Char>
260struct std::formatter<comms::Id<Tag, Repr>, Char> : std::formatter<Repr, Char> {
261 template <class FormatContext>
262 auto format(const comms::Id<Tag, Repr>& id, FormatContext& ctx) const {
263 return std::formatter<Repr, Char>::format(id.value(), ctx);
264 }
265};
266
267// -- macros ------------------------------------------------------------------
268// Defined at file scope (after the std specializations) so they work from any
269// namespace; the generated aliases fully qualify the commons types. `Name`
270// appears as a `using`-declared identifier and inside a template argument, so
271// it can't be parenthesized — silence the bugprone-macro-parentheses warning
272// over the whole block.
273// NOLINTBEGIN(bugprone-macro-parentheses)
274
278#define COMMONS_DEFINE_ID_TAG(Name, NameStr) \
279 struct Name##Tag { \
280 [[maybe_unused]] static constexpr ::std::string_view name = NameStr; \
281 }
282
283#define COMMONS_DEFINE_UINT8_ID(Name, NameStr) \
284 COMMONS_DEFINE_ID_TAG(Name, NameStr); \
285 using Name = ::comms::Uint8Id<Name##Tag>
286
287#define COMMONS_DEFINE_UINT16_ID(Name, NameStr) \
288 COMMONS_DEFINE_ID_TAG(Name, NameStr); \
289 using Name = ::comms::Uint16Id<Name##Tag>
290
291#define COMMONS_DEFINE_UINT32_ID(Name, NameStr) \
292 COMMONS_DEFINE_ID_TAG(Name, NameStr); \
293 using Name = ::comms::Uint32Id<Name##Tag>
294
295#define COMMONS_DEFINE_UINT64_ID(Name, NameStr) \
296 COMMONS_DEFINE_ID_TAG(Name, NameStr); \
297 using Name = ::comms::Uint64Id<Name##Tag>
298
299#define COMMONS_DEFINE_STRING_ID(Name, NameStr) \
300 COMMONS_DEFINE_ID_TAG(Name, NameStr); \
301 using Name = ::comms::StringId<Name##Tag>
302
303#if COMMONS_WITH_ULID
304#define COMMONS_DEFINE_ULID_ID(Name, NameStr) \
305 COMMONS_DEFINE_ID_TAG(Name, NameStr); \
306 using Name = ::comms::UlidId<Name##Tag>
307#endif
308
309// NOLINTEND(bugprone-macro-parentheses)
Strong-typed identifier: a Repr value tagged with a phantom Tag.
Definition id.hpp:106
Id(std::string_view sv)
Convenience for StringId<Tag>: build directly from a string_view.
Definition id.hpp:129
Id()=delete
Default-construct the underlying Repr when it is itself default-initializable; otherwise the construc...
std::string to_string(const Color &c)
Color as its canonical hex string (#RRGGBB, or #RRGGBBAA when not opaque).
Definition color.hpp:1388
Concept form of is_allowed_id_repr.
Definition id.hpp:82
A tag exposing a static constexpr std::string_view name, used by display_string and the COMMONS_DEFIN...
Definition id.hpp:87
Central feature-gate header for Commons' optional integrations.
const Repr & to_underlying(const Id< Tag, Repr > &id) noexcept
Returns the underlying representation by const reference.
Definition id.hpp:191
std::string display_string(const Id< Tag, Repr > &id)
Tag::name + "/" + to_string(id) for a NamedIdTag, just to_string(id) otherwise.
Definition id.hpp:221
Trait selecting the representations allowed inside Id<Tag, Repr>.
Definition id.hpp:60
Fixed-width numeric aliases shared across the C++ libraries.