|
commons 0.1.5
Header-only C++23 library of common/shared types for the C++ libraries
|
A header-only C++23 library of small, shared building-block types — a compile-time fixed-size string, Rust-flavoured fixed-width numeric aliases, an RGBA Color with full HSL/HSV manipulation and CSS/Material-UI palettes, an Iconify Icon identifier, presentation metadata (DisplayInfo), a compile-time named-Flag system, a Spring-style Prioritized ordering toolkit, SemVer / VersionConstraint semantic-version types, an IOrigin provenance envelope, and a strong-typed Id<Tag, Repr> identifier. Every type carries optional nlohmann/json serialization that turns on by itself when the dependency is available; Id<Tag, ulid::Ulid> lights up the same way when cpp-ulid is on the include path. The namespace is comms; headers live under <commons/...>.
FixedString, Color, and Icon are literal types usable in constexpr and static_assert contexts and as non-type template parameters.PrioritizedSet and FlagSet are designed for config-sized collections and use linear-time lookups.FixedString is built with class template argument deduction (CTAD) straight from the literal, so you never spell its size. It is a structural type, which means it can also appear directly in a template argument list, e.g. Event<"order.created">. The numeric aliases (u32, f64, …) are lowercase names for the standard fixed-width types. Including commons/commons.hpp pulls in every core type plus the self-gating JSON hooks; it is always safe to include even when nlohmann/json is absent.
commons is a header-only INTERFACE library. There is no compiled artifact to link — you only need the headers on your include path and C++23 enabled.
Package-manager support (vcpkg, Conan, system packages) is not provided. The supported integration paths are CMake, Meson, and copying the headers.
The most reliable option: drop the repository into your tree (a submodule or copy) and add it.
Linking commons::commons brings the include directory and the cxx_std_23 requirement along with it.
By default no optional dependency is fetched: the JSON hooks and the ULID Id repr both auto-detect their backing headers. To force an integration on — which additionally fetches the dependency and hard-defines the gate macro — configure with -DCOMMONS_WITH_NLOHMANN_JSON=ON (fetches nlohmann/json 3.12.0 or newer) and/or -DCOMMONS_WITH_ULID=ON (fetches cpp-ulid 1.0.0).
After cmake --install, the package is consumable via find_package:
Install rules are automatically disabled when nlohmann/json or cpp-ulid was fetched (a fetched dependency cannot be re-exported). For a clean install, leave the gate options OFF (the default) or provide the dependencies through system packages.
Meson options mirror the CMake ones: -Djson=true|false, -Dulid=true|false, -Dtests=true|false, -Dexamples=true|false. A pkg-config file is generated on install.
Copy include/commons onto your include path and compile with C++23. The only generated header is commons/version.hpp: CMake and Meson produce it from commons/version.hpp.in using the project version. For a pure manual copy, either configure once with CMake/Meson and copy the generated commons/version.hpp alongside the rest, or create it by hand from the template (replace the four @PROJECT_VERSION...@ tokens). It is only needed if you include the umbrella commons/commons.hpp or commons/version.hpp directly.
constexpr std::string_view, std::format, and concepts. CMake enforces this with target_compile_features(commons INTERFACE cxx_std_23).<commons/json.hpp>; cpp-ulid 1.0.0 enables comms::Id<Tag, ulid::Ulid>.A fixed-capacity string whose contents are fixed at compile time. N is the buffer size including the trailing null terminator, so size() returns N - 1. Because it is a structural type, it can be a non-type template parameter.
It converts implicitly to std::string_view, and operator== compares against any FixedString<M> (different sizes simply compare unequal).
Four u8 channels (r, g, b, a), with almost the entire API constexpr: packed-integer conversions, HSL/HSV conversion, channel and alpha tweaks, the HSL transforms, WCAG luminance/contrast, palette generation, and parsing. Only the std::string-producing methods are non-constexpr. The default Color is opaque black.
A value type holding an Iconify set:name identifier (e.g. mdi:abacus) inline in a 64-byte buffer — no heap, trivially copyable, usable in constexpr contexts. Construct it from a whole value or from the two parts; both validate.
Optional presentation metadata — name, description, icon, color, every field an std::optional. The intent is static data attached to a type once and never mutated. The icon/color fields reuse Icon/Color, so they serialize to JSON for a frontend out of the box.
Compile-time named flags grouped into categories, a runtime FlagSet that keeps insertion order, a program-wide GlobalFlagRegistry, and mixins for types that own a flag set. Flags are types, declared (and optionally auto-registered) with the COMMONS_*_FLAG* macros.
A polymorphic provenance envelope — where a definition came from — for an open set of sources. The kind() discriminator is a compile-time FixedString template parameter: derive from OriginKind<"yourkind", YourType> and you get kind(), a deep clone(), and a DisplayInfo-backed info() (so every origin is also Displayable). Built-ins are CoreOrigin, InternalOrigin, ExternalOrigin (carrying a source), and UnknownOrigin; carry one as an OriginPtr (std::unique_ptr<IOrigin>). New kinds self-register into the GlobalOriginRegistry with COMMONS_REGISTER_ORIGIN(Type) — mirroring the flag registry — so a JSON kind can be resolved back to the right type.
Attaches integer priorities to orderable things and sorts them deterministically, mirroring Spring's Ordered: lower value sorts first (higher precedence). HIGHEST_PRECEDENCE is INT_MIN, LOWEST_PRECEDENCE is INT_MAX, and the neutral DEFAULT_PRIORITY is 0.
A Semantic Versioning 2.0.0 value — major.minor.patch plus optional prerelease and build metadata. SemVer::parse is non-throwing (returns std::optional), accepts an optional v prefix, and parses partial versions leniently ("1", "1.2"). Ordering implements the full §11 prerelease precedence — a prerelease ranks below its release and numeric identifiers compare numerically, so 1.0.0-alpha.2 < 1.0.0-alpha.10 < 1.0.0-beta < 1.0.0 — while build metadata is preserved in the text form but ignored by comparison and equality. Because it holds std::string members it is a runtime type (not constexpr).
An npm-style semver range that answers satisfies(SemVer): *, an exact version, the comparisons >=/>/<=/</!=, caret (^1.2.3 → >=1.2.3 <2.0.0) and tilde (~1.2.3 → >=1.2.3 <1.3.0) ranges, and space-separated intersections like >=1.0.0 <2.0.0 (all must match). Unlike SemVer::parse, VersionConstraint::parse throws std::invalid_argument on a malformed sub-version.
A strong-typed identifier: a Repr value tagged with a phantom Tag so ids of different kinds cannot be mixed even when the underlying representation is identical. The allowed reprs are deliberately narrow — the unsigned fixed-width ints (std::uint8_t / 16 / 32 / 64), std::string, and — gated by COMMONS_WITH_ULID — ulid::Ulid. Aliases Uint8Id<Tag> … Uint64Id<Tag>, StringId<Tag>, and UlidId<Tag> save typing; the COMMONS_DEFINE_UINT{8,16,32,64}_ID, COMMONS_DEFINE_STRING_ID, and COMMONS_DEFINE_ULID_ID macros emit both a Name##Tag (with a static constexpr std::string_view name) and the matching using alias in one shot. to_string delegates to the underlying repr; display_string prefixes it with Tag::name for named tags; and the std::formatter<Id> specialization inherits from std::formatter<Repr> so any spec the wrapped type accepts (e.g. "{:#x}" for the uint reprs) works transparently.
This covers the main paths: successful parsing (with the mandatory optional check), the palette accessors, the WCAG helpers, and the formatter specs. fade(opacity) takes a [0, 1] opacity and sets the alpha channel. Transforms such as lighten/darken/saturate/rotate_hue clamp their results, so they never produce an out-of-range channel.
Pitfall — invalid shades throw.
mui::red[shade]accepts only50, 100, 200, …, 900, andaccent(shade)only100, 200, 400, 700. Any other value throwsstd::out_of_range. The flat aliases (red_500,red_a200) cannot be misindexed, so prefer them for fixed shades.
Use Icon::parse for runtime input — it returns std::nullopt on malformed values. Use Icon::from when you want a hard failure: it throws std::invalid_argument for a malformed value or std::length_error for one that exceeds the 64-byte capacity. In a constexpr context, either failure becomes a compile error.
Pitfall — predefined catalogs are not in the umbrella. The MDI table has 7,447 entries, so
commons/commons.hppdoes not include it. Add#include <commons/icons.hpp>in the translation units that needcomms::Icons::mdi::....
There are two ways to attach DisplayInfo, and a concept to detect it.
comms::display_info<T>() dispatches to whichever mechanism is present. comms::Displayable<T> reports whether either exists, so you can constrain templates on it. Calling display_info<T>() on a type that has neither is a compile error, by design.
FlagSet deduplicates by flag name and keeps insertion order; insert returns false when the name is already present. group_by_category() returns a std::map from category name to the flags in it. The COMMONS_DEFINE_FLAG* macros register each flag into the GlobalFlagRegistry automatically; the builder mixins (FlagBuilderMixin for a private set, FlagBuilderGetters for an observable one) constrain their typed overloads to the listed categories.
get_priority(x) is a uniform, null-safe lookup that works on values, references, raw pointers, and smart pointers, falling back to DEFAULT_PRIORITY when no priority is discoverable. PrioritizedCompare and LenientPrioritizedCompare<T> order std::shared_ptrs for use as the comparator of a std::set.
Pitfall —
insertnever updates an existing priority. Likestd::set, re-inserting an equal value is a no-op; the originally stored priority stays. Useset_priority(value, p)to change it. Also notePrioritizedSet'sinsert/find/erase(value)are O(n) — it targets config-sized collections, not large data sets.
SemVer works directly in std::set/std::map/std::sort (via its operator<=>) and in std::unordered_* (via the std::hash specialization); both SemVer and VersionConstraint also support std::format, operator<<, and JSON.
With nlohmann/json available, every public type gains to_json/from_json.
The mappings are: FixedString and Icon ⇄ strings; Color ⇄ a hex string (#RRGGBB, or #RRGGBBAA when not opaque); Hsl/Hsv ⇄ objects; DisplayInfo ⇄ an object with absent fields omitted; FlagRef ⇄ its name and FlagSet ⇄ an array of names; SemVer ⇄ its canonical version string and VersionConstraint ⇄ its raw range string; IOrigin/OriginPtr ⇄ a {"kind", …fields} object (null OriginPtr ⇄ null), with the four built-in kinds round-tripping their fields and the kind resolved back through the GlobalOriginRegistry (an unknown kind throws; a custom kind brings its own to_json/from_json); i128/u128 ⇄ decimal strings; the complex aliases ⇄ [real, imaginary] arrays; WithPriority<T> ⇄ {"priority":N,"value":<T>} and PrioritizedSet<T> ⇄ a sorted array — both only when T is itself JSON-serializable; Id<Tag, Repr> ⇄ the inner Repr's natural JSON (a number for the uint reprs, a string for std::string, the ULID string when ULID is also enabled).
The library uses three distinct strategies, by type:
std::optional for parsing.** Color::parse, Color::parse_hex, and Icon::parse return std::nullopt on malformed input. Check before dereferencing.Icon::from throws std::invalid_argument (malformed) or std::length_error (too long). Color's MUI shade accessors (operator[], accent) throw std::out_of_range. The std::format specializations throw std::format_error on a bad spec. The JSON from_json hooks throw nlohmann's exception type when a value is invalid (an unparseable color, a string too long for a FixedString, an unregistered flag name, …)._color and _icon user-defined literals are consteval, so a malformed literal fails to compile. Icon::from in a constexpr context turns its throws into compile errors. Calling display_info<T>() on a non-Displayable type is a compile error.FixedString size counts the null terminator.** FixedString{"hi"} has N == 3 and size() == 2. When you need to name the type explicitly for JSON, use the size with the terminator: a 13-character string round-trips through FixedString<14>.FixedString JSON overflow throws.** Deserializing a string longer than the fixed capacity is an error, not a silent truncation.parse dereference is undefined behavior. Color::parse(...) and Icon::parse(...) return std::optional; calling -> on a nullopt result is UB. Always check.red_500) for compile-time-fixed shades.i128/u128 are only defined when the compiler provides 128-bit integers. Guard their use with #if defined(COMMONS_HAS_INT128). They have no default operator<<; in JSON they travel as decimal strings to avoid lossy narrowing.PrioritizedSet snapshots priority at insert.** Mutating an element's own priority afterward does not reorder the set; use set_priority. clear() does not reset the internal insertion-order counter.PrioritizedSet from_json needs recoverable priority.** Reading a set back works only when T derives from Prioritized or is otherwise Prioritizable; for a plain T, the set is serialize-only.WithPriority<T> flavor depends on T.** For a non-final class it inherits T (a true is-a T); for final classes and fundamentals it composes, exposing the value through value() / operator* / operator->. Either way, the constructor's first argument is the priority.GlobalFlagRegistry is populated at static initialization; do not query it before main.Thread safety is not documented. The value types (FixedString, Color, Icon, DisplayInfo) are plain data and safe to read concurrently when not mutated. FlagSet, PrioritizedSet, and the GlobalFlagRegistry are not synchronized; treat concurrent mutation as unsafe.
| Header | Provides |
|---|---|
commons/commons.hpp | Umbrella header (all core types + JSON hooks). |
commons/version.hpp | Generated from version.hpp.in by the build: COMMONS_VERSION_MAJOR/MINOR/PATCH/STRING macros and the comms::version / version_major / version_minor / version_patch constants. |
commons/types.hpp | i8…u64, f32/f64, usize/isize, complex aliases (cs8…cs64, cu8…cu64, cf32/cf64), and i128/u128 (gated by COMMONS_HAS_INT128). |
commons/fixed_string.hpp | comms::FixedString<N> — structural, NTTP-friendly fixed string. |
commons/color.hpp | comms::Color, comms::Hsl/comms::Hsv, and comms::Colors::css / comms::Colors::mui palettes. |
commons/icon.hpp | comms::Icon — an Iconify set:name identifier; Icon::from / Icon::parse. |
commons/icons.hpp | Opt-in predefined catalogs: comms::Icons::mdi::.... Not pulled by the umbrella. |
commons/literals.hpp | The comms::literals user-defined literals: "#6366f1"_color and "mdi:home"_icon (both consteval). |
commons/display_info.hpp | comms::DisplayInfo, the comms::HasDisplayInfo<T> trait, free comms::display_info<T>(), and the comms::Displayable<T> concept. |
commons/flag.hpp | comms::Flag/FlagCategory, FlagRef, FlagSet, GlobalFlagRegistry, the IHasFlags/HasFlags/FlagBuilderMixin/FlagBuilderGetters mixins, and the COMMONS_*_FLAG* macros. |
commons/origin.hpp | comms::IOrigin/OriginPtr, the OriginKind<FixedString, Derived> CRTP base, built-in Core/Internal/External/Unknown origins, GlobalOriginRegistry, COMMONS_REGISTER_ORIGIN. |
commons/prioritized.hpp | comms::Prioritized, get_priority, the comparators, PrioritizedSet<T>, PrioritizedBuilder<Derived>, and WithPriority<T> / with_priority / make_prioritized. |
commons/semver.hpp | comms::SemVer — a Semantic Versioning 2.0.0 value; non-throwing SemVer::parse, full §11 ordering, std::hash. |
commons/version_constraint.hpp | comms::VersionConstraint — an npm-style semver range answering satisfies(SemVer); VersionConstraint::parse throws on a malformed sub-version. |
commons/id.hpp | comms::Id<Tag, Repr> — strong-typed identifier; Uint{8,16,32,64}Id/StringId/UlidId aliases, to_string/display_string, and the COMMONS_DEFINE_*_ID macros. |
commons/config.hpp | The COMMONS_WITH_* feature-gate macros. |
commons/json.hpp | Optional nlohmann/json hooks (inert unless the dependency is present). |
Each example is a self-contained program under examples/.
| Example | Demonstrates |
|---|---|
examples/hello.cpp | FixedString, the numeric aliases, and version. |
examples/color.cpp | Parsing, hex/CSS output, HSL transforms, palettes, WCAG, and the formatter specs. |
examples/icon.cpp | Predefined icons, ad-hoc construction, the set/name accessors, and text output. |
examples/display_info.cpp | Intrusive and non-intrusive DisplayInfo attachment and the Displayable concept. |
examples/flag.cpp | FlagSet, the global registry, and a category-constrained builder read through IHasFlags. |
examples/origin.cpp | IOrigin kinds, clone(), the DisplayInfo description, and registry resolution by kind. |
examples/prioritized.cpp | The builder mixin, both WithPriority flavors, PrioritizedSet, and the comparators. |
examples/semver.cpp | Parsing, full-precedence sorting, std::format, and VersionConstraint range checks. |
examples/id.cpp | The Id<Tag, Repr> macros, display_string, inherited formatter specs, and the ULID repr. |
examples/json_integration.cpp | The optional nlohmann/json round-trips (requires the integration). |
examples/consumers/fetch_content/ | A standalone downstream project that pulls commons via FetchContent. |
The test suite uses GoogleTest. With the bundled Makefile:
Equivalently, with raw CMake:
The JSON tests (tests/test_json.cpp) are compiled only when the integration is enabled. With Meson:
Add -Djson=true and/or -Dulid=true to force the respective integration under Meson.
Do I need to link a library? No. commons is header-only; linking commons::commons only adds the include path and the C++23 requirement.
What happens if I give Color::parse or Icon::parse bad input? They return std::nullopt. The from factory on Icon throws instead, and the _color literal fails to compile.
Can I use it in multiple threads? Thread safety is not documented. The plain value types are safe to read concurrently; the registries and the mutable collections are not synchronized.
Does FixedString own its characters? Yes — it stores them inline. view() returns a std::string_view into that storage, so do not let the view outlive the FixedString.
Why won't comms::Icons::mdi::... compile? Add #include <commons/icons.hpp>; the predefined catalogs are intentionally left out of the umbrella header.
How do I get the JSON hooks? Include <commons/json.hpp> (or the umbrella, which includes it) and make sure <nlohmann/json.hpp> is reachable. To force the dependency to be fetched and linked, configure with -DCOMMONS_WITH_NLOHMANN_JSON=ON (CMake) or -Djson=true (Meson).
How do I get the ULID Id repr? comms::Id<Tag, ulid::Ulid> (and its UlidId<Tag> / COMMONS_DEFINE_ULID_ID shortcuts) light up when <ulid/ulid.h> is on the include path. To force the dependency to be fetched and linked, configure with -DCOMMONS_WITH_ULID=ON (CMake) or -Dulid=true (Meson).
Contributions to the library are welcome! If you encounter any issues or have suggestions for improvements, please feel free to submit a pull request or open an issue on the project's repository.
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.