commons 0.1.5
Header-only C++23 library of common/shared types for the C++ libraries
Loading...
Searching...
No Matches
flag.hpp
Go to the documentation of this file.
1#pragma once
2
45
48#include <commons/types.hpp>
49
50#include <algorithm>
51#include <concepts>
52#include <map>
53#include <optional>
54#include <string_view>
55#include <utility>
56#include <vector>
57
58namespace comms {
59
60// ---------------------------------------------------------------------------
61// Compile-time flag / category types
62// ---------------------------------------------------------------------------
63
66template <FixedString Name>
68 static constexpr std::string_view name = Name.view();
69};
70
72struct UnsetFlagCategory : FlagCategory<"unset"> {};
73
77template <FixedString Name, typename Category = UnsetFlagCategory>
78struct Flag {
79 static constexpr std::string_view name = Name.view();
80 using category = Category;
81 static constexpr std::string_view category_name = Category::name;
82};
83
84// ---------------------------------------------------------------------------
85// Concepts
86// ---------------------------------------------------------------------------
87
90template <typename C>
91concept AnyFlagCategory = requires {
92 { C::name } -> std::convertible_to<std::string_view>;
93};
94
97template <typename F>
98concept AnyFlag = requires {
99 { F::name } -> std::convertible_to<std::string_view>;
100 typename F::category;
102};
103
105template <typename F, typename Category>
106concept FlagInCategory = AnyFlag<F> && std::same_as<typename F::category, Category>;
107
108// ---------------------------------------------------------------------------
109// FlagRef — type-erased runtime handle
110// ---------------------------------------------------------------------------
111
118struct FlagRef {
119 std::string_view name;
120 std::string_view category;
121 const DisplayInfo* display = nullptr;
122
125 template <AnyFlag F>
126 [[nodiscard]] static FlagRef of() noexcept {
127 FlagRef r{.name = F::name, .category = F::category::name};
128 if constexpr (Displayable<F>) {
129 r.display = &display_info<F>();
130 }
131 return r;
132 }
133
134 bool operator==(const FlagRef& o) const noexcept {
135 return name == o.name;
136 }
137};
138
139// ---------------------------------------------------------------------------
140// FlagSet — insertion-ordered unique set
141// ---------------------------------------------------------------------------
142
147class FlagSet {
148 std::vector<FlagRef> items_;
149
150public:
153 template <AnyFlag F>
154 bool insert() {
155 return insert(FlagRef::of<F>());
156 }
157
159 bool insert(const FlagRef& f) {
160 if (contains(f.name)) {
161 return false;
162 }
163 items_.push_back(f);
164 return true;
165 }
166
169 template <AnyFlag... Fs>
170 [[nodiscard]] static FlagSet of() {
171 FlagSet s;
172 (s.insert<Fs>(), ...);
173 return s;
174 }
175
176 template <AnyFlag F>
177 [[nodiscard]] bool contains() const {
178 return contains(F::name);
179 }
180
181 [[nodiscard]] bool contains(std::string_view name) const {
182 return std::ranges::any_of(items_, [name](const FlagRef& f) { return f.name == name; });
183 }
184
186 [[nodiscard]] std::size_t count(const std::string_view name) const {
187 return contains(name) ? 1U : 0U;
188 }
189
190 template <AnyFlag F>
191 bool erase() {
192 return erase(F::name);
193 }
194
196 bool erase(const std::string_view name) {
197 for (auto it = items_.begin(); it != items_.end(); ++it) {
198 if (it->name == name) {
199 items_.erase(it);
200 return true;
201 }
202 }
203 return false;
204 }
205
206 void clear() noexcept {
207 items_.clear();
208 }
209
210 [[nodiscard]] bool empty() const noexcept {
211 return items_.empty();
212 }
213
214 [[nodiscard]] std::size_t size() const noexcept {
215 return items_.size();
216 }
217
218 [[nodiscard]] auto begin() const noexcept {
219 return items_.begin();
220 }
221
222 [[nodiscard]] auto end() const noexcept {
223 return items_.end();
224 }
225
228 [[nodiscard]] std::map<std::string_view, std::vector<FlagRef>> group_by_category() const {
229 std::map<std::string_view, std::vector<FlagRef>> groups;
230 for (const auto& f : items_) {
231 groups[f.category].push_back(f);
232 }
233 return groups;
234 }
235
236 bool operator==(const FlagSet&) const = default; // order-sensitive
237};
238
239// ---------------------------------------------------------------------------
240// GlobalFlagRegistry — Meyers singleton
241// ---------------------------------------------------------------------------
242
246 FlagSet flags_;
247 GlobalFlagRegistry() = default;
248
249public:
250 GlobalFlagRegistry(const GlobalFlagRegistry&) = delete;
251 GlobalFlagRegistry& operator=(const GlobalFlagRegistry&) = delete;
253 GlobalFlagRegistry& operator=(GlobalFlagRegistry&&) = delete;
254 ~GlobalFlagRegistry() = default;
255
256 [[nodiscard]] static GlobalFlagRegistry& instance() noexcept {
257 static GlobalFlagRegistry r;
258 return r;
259 }
260
261 template <AnyFlag F>
262 bool add() {
263 return flags_.insert<F>();
264 }
265
266 bool add(const FlagRef& f) {
267 return flags_.insert(f);
268 }
269
270 [[nodiscard]] const FlagSet& flags() const noexcept {
271 return flags_;
272 }
273
275 [[nodiscard]] std::optional<FlagRef> find(const std::string_view name) const {
276 for (const auto& f : flags_) {
277 if (f.name == name) {
278 return f;
279 }
280 }
281 return std::nullopt;
282 }
283
284 [[nodiscard]] auto group_by_category() const {
285 return flags_.group_by_category();
286 }
287};
288
292template <AnyFlag F>
294 FlagRegistrar() noexcept {
295 GlobalFlagRegistry::instance().add<F>();
296 }
297};
298
299// ---------------------------------------------------------------------------
300// Mixins for types that own a FlagSet
301// ---------------------------------------------------------------------------
302
306template <typename F, typename... Categories>
307concept AllowedFlag =
308 AnyFlag<F> && (sizeof...(Categories) == 0 || (FlagInCategory<F, Categories> || ...));
309
315public:
316 virtual ~IHasFlags() = default;
317
319 [[nodiscard]] virtual const FlagSet& flags() const noexcept = 0;
320
321 template <AnyFlag F>
322 [[nodiscard]] bool has_flag() const {
323 return flags().template contains<F>();
324 }
325
326 [[nodiscard]] bool has_flag(const std::string_view name) const {
327 return flags().contains(name);
328 }
329
330protected:
331 // Protected to prevent slicing through the interface; derived types remain
332 // copyable/movable via their own (implicitly defined) operations.
333 IHasFlags() = default;
334 IHasFlags(const IHasFlags&) = default;
335 IHasFlags(IHasFlags&&) = default;
336 IHasFlags& operator=(const IHasFlags&) = default;
337 IHasFlags& operator=(IHasFlags&&) = default;
338};
339
346template <typename... Categories>
347class HasFlags : public IHasFlags {
348public:
349 [[nodiscard]] const FlagSet& flags() const noexcept override {
350 return flags_;
351 }
352
354 template <AnyFlag F>
355 static constexpr bool flag_allowed = AllowedFlag<F, Categories...>;
356
357protected:
358 FlagSet flags_;
359};
360
372template <typename Derived, typename... Categories>
374public:
376 template <AnyFlag F>
377 static constexpr bool flag_allowed = AllowedFlag<F, Categories...>;
378
381 template <AnyFlag F>
382 requires AllowedFlag<F, Categories...>
383 Derived& insert_flag() {
384 flags_.template insert<F>();
385 return self();
386 }
387
388 Derived& insert_flag(const FlagRef& f) {
389 flags_.insert(f);
390 return self();
391 }
392
395 template <AnyFlag F>
396 requires AllowedFlag<F, Categories...>
397 Derived& flag() {
398 return insert_flag<F>();
399 }
400
401 Derived& flag(const FlagRef& f) {
402 return insert_flag(f);
403 }
404
406 template <AnyFlag F>
407 requires AllowedFlag<F, Categories...>
408 Derived& set_flag(const bool on) {
409 if (on) {
410 flags_.template insert<F>();
411 } else {
412 flags_.template erase<F>();
413 }
414 return self();
415 }
416
417 template <AnyFlag F>
418 Derived& remove_flag() {
419 flags_.template erase<F>();
420 return self();
421 }
422
423 Derived& remove_flag(const std::string_view name) {
424 flags_.erase(name);
425 return self();
426 }
427
430 flags_ = std::move(flags);
431 return self();
432 }
433
434 Derived& clear_flags() {
435 flags_.clear();
436 return self();
437 }
438
439protected:
440 FlagSet flags_;
441
444 [[nodiscard]] const FlagSet& flags() const noexcept {
445 return flags_;
446 }
447
448private:
449 Derived& self() noexcept {
450 return static_cast<Derived&>(*this);
451 }
452};
453
458template <typename Derived, typename... Categories>
459class FlagBuilderGetters : public FlagBuilderMixin<Derived, Categories...>, public IHasFlags {
460public:
461 [[nodiscard]] const FlagSet& flags() const noexcept override {
462 return this->flags_;
463 }
464};
465
466} // namespace comms
467
468// ---------------------------------------------------------------------------
469// Declaration macros (define-then-use, mirroring COMMONS_MUI_FAMILY). Each
470// expands to a declaration; the caller supplies the trailing `;`.
471//
472// Registration caveat: the `inline` registrar registers once at static init
473// (the Meyers singleton guarantees the registry exists first), so query the
474// registry at or after `main`. In a *static library*, an unreferenced registrar
475// may be stripped unless the archive is linked whole (e.g. `--whole-archive` /
476// `-force_load`); reference the flag, or register explicitly, if that matters.
477// ---------------------------------------------------------------------------
478
480#define COMMONS_FLAG_CATEGORY(Ident, Name) \
481 struct Ident : ::comms::FlagCategory<Name> {}
482
484#define COMMONS_FLAG(Ident, Name) \
485 struct Ident : ::comms::Flag<Name> {}
486
488#define COMMONS_FLAG_IN(Ident, Name, Cat) \
489 struct Ident : ::comms::Flag<Name, Cat> {}
490
492// NOLINTNEXTLINE(bugprone-macro-parentheses)
493#define COMMONS_REGISTER_FLAG(Ident) \
494 inline const ::comms::FlagRegistrar<Ident> commons_flag_registrar_##Ident {}
495
497// NOLINTNEXTLINE(bugprone-macro-parentheses)
498#define COMMONS_DEFINE_FLAG(Ident, Name) \
499 struct Ident : ::comms::Flag<Name> {}; \
500 inline const ::comms::FlagRegistrar<Ident> commons_flag_registrar_##Ident
501
503// NOLINTNEXTLINE(bugprone-macro-parentheses)
504#define COMMONS_DEFINE_FLAG_IN(Ident, Name, Cat) \
505 struct Ident : ::comms::Flag<Name, Cat> {}; \
506 inline const ::comms::FlagRegistrar<Ident> commons_flag_registrar_##Ident
507
511// `ConceptName` is a concept name and `FlagTmpl` a template name — neither can be
512// parenthesized, so the macro-parentheses check is suppressed here.
513// NOLINTBEGIN(bugprone-macro-parentheses)
514#define COMMONS_FLAG_FAMILY(CatIdent, Name, FlagTmpl, ConceptName) \
515 struct CatIdent : ::comms::FlagCategory<Name> {}; \
516 template <::comms::FixedString N> \
517 struct FlagTmpl : ::comms::Flag<N, CatIdent> {}; \
518 template <typename F> \
519 concept ConceptName = ::comms::FlagInCategory<F, CatIdent>
520// NOLINTEND(bugprone-macro-parentheses)
A builder that is also observable: combines FlagBuilderMixin's fluent mutators with a public,...
Definition flag.hpp:459
const FlagSet & flags() const noexcept override
The owned flag set (insertion order preserved).
Definition flag.hpp:461
CRTP helper for builders.
Definition flag.hpp:373
Derived & flag()
Alias for insert_flag that reads naturally in a fluent chain (builder.flag<F>()).
Definition flag.hpp:397
Derived & set_flags(FlagSet flags)
Replace the whole set.
Definition flag.hpp:429
static constexpr bool flag_allowed
True when a flag in F's category is permitted by this builder's constraint.
Definition flag.hpp:377
Derived & insert_flag()
Insert flag F (no-op if already present).
Definition flag.hpp:383
Derived & set_flag(const bool on)
Set flag F's presence: insert when on, erase otherwise.
Definition flag.hpp:408
const FlagSet & flags() const noexcept
Read access kept protected and used internally; FlagBuilderGetters publishes it through IHasFlags.
Definition flag.hpp:444
An insertion-ordered set of unique flags, deduplicated by name.
Definition flag.hpp:147
bool insert(const FlagRef &f)
Insert a FlagRef, deduplicating by name. Returns true if newly added.
Definition flag.hpp:159
static FlagSet of()
Build a set from a flag type pack, inserting each in order.
Definition flag.hpp:170
bool insert()
Insert a flag by type.
Definition flag.hpp:154
bool erase(const std::string_view name)
Erase the flag with the given name. Returns true if one was removed.
Definition flag.hpp:196
std::map< std::string_view, std::vector< FlagRef > > group_by_category() const
Flags grouped by category name.
Definition flag.hpp:228
std::size_t count(const std::string_view name) const
0 or 1 — flags are unique by name, so this mirrors std::set::count.
Definition flag.hpp:186
A program-wide registry of every known flag.
Definition flag.hpp:245
std::optional< FlagRef > find(const std::string_view name) const
Resolve a flag by name. Used by the JSON from_json path.
Definition flag.hpp:275
Implementation of IHasFlags: embeds a FlagSet and exposes it publicly.
Definition flag.hpp:347
const FlagSet & flags() const noexcept override
The owned flag set (insertion order preserved).
Definition flag.hpp:349
static constexpr bool flag_allowed
True when a flag in F's category is permitted by this set's constraint.
Definition flag.hpp:355
Abstract read-only view of a type that owns a FlagSet.
Definition flag.hpp:314
virtual const FlagSet & flags() const noexcept=0
The owned flag set (insertion order preserved).
Whether flag F may go into a set constrained to Categories...: any flag when the list is empty (uncon...
Definition flag.hpp:307
Any type that exposes a name convertible to std::string_view — satisfied by FlagCategory<Name> and ty...
Definition flag.hpp:91
Any flag type: a name plus a category member type that is itself an AnyFlagCategory.
Definition flag.hpp:98
True when T has display metadata — via a member display_info() or a HasDisplayInfo<T> specialization.
Definition display_info.hpp:82
A flag whose category is exactly Category.
Definition flag.hpp:106
Presentation metadata for a type/value — name, description, icon, color, all optional — plus the trai...
NTTP-friendly fixed-size string usable as a non-type template parameter.
Presentation metadata for a type/value.
Definition display_info.hpp:43
A flag category, identified by a FixedString name.
Definition flag.hpp:67
A type-erased, non-owning runtime handle to a flag.
Definition flag.hpp:118
std::string_view name
Flag name; also the identity used for ==.
Definition flag.hpp:119
const DisplayInfo * display
null when the flag is not Displayable
Definition flag.hpp:121
std::string_view category
Owning category name.
Definition flag.hpp:120
static FlagRef of() noexcept
Build a FlagRef from a compile-time flag type, capturing its display metadata only if F is Displayabl...
Definition flag.hpp:126
A self-registering object: constructing one registers F into the GlobalFlagRegistry.
Definition flag.hpp:293
A compile-time flag: a distinct type named by Name and belonging to Category (defaulting to UnsetFlag...
Definition flag.hpp:78
The default category for a Flag declared without an explicit one.
Definition flag.hpp:72
Fixed-width numeric aliases shared across the C++ libraries.