stduuid, a C++17 single-header implementation of proposal P0959
A C++17 cross-platform implementation for UUIDs
At a glance
- What is it?
- stduuid is a single-header UUID library for C++17 that implements the standard proposal P0959, with random, name-based and operating system generators in a uuids namespace. It tracks a proposal that is still moving, and two of its generators are behind macros.
- Who is it for?
- stduuid suits a C++17 codebase that wants typed UUIDs with version and variant checks, unordered container support and stream formatting, without taking a dependency on a heavier framework. It does not suit a project that needs the time-based generator, which the library marks experimental and warns against in production, or one that assumes a finalised standard API.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 71 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A uuid type in the uuids namespace, not std
stduuid is a C++17 cross-platform single-header library for universally unique identifiers, known as UUID or, mostly on Windows, GUID. A UUID is a 128-bit number used to identify information in computer systems, and the README lists database table keys, COM interfaces, classes and type libraries among the uses.
The most consequential design decision is the namespace. The specification puts the uuid library in the std namespace, but this implementation uses uuids instead, in order to make the library usable without violating the restrictions imposed on the std namespace. That single choice shapes the whole API. Every example in the README qualifies its types, uuids::uuid, uuids::uuid_random_generator and the rest, and code that adopts the library has to import that namespace rather than expecting the types to appear next to the standard ones.
The library is an implementation of the proposal P0959, whose file sits at the root of the repository alongside P0959.md and the README. The README states plainly that as the proposal evolves based on standard committee and C++ community feedback, this implementation will reflect those changes. That is a promise of fidelity to a moving target, and it is the main thing to understand before depending on the API shape.
The licence is MIT. The last push to the repository was on 2026-08-13, while the newest tagged release is v1.2.3 from 2023-01-25, so a consumer picking up a release tarball is getting code that predates three years of commits.
uuid carries its version and variant as types
The type model is small and deliberately narrow. There is a uuid class, default constructible to a nil UUID, constructible from a range defined by a pair of iterators, or from a span. There is uuid_variant, a strongly typed enum representing the type of a UUID, and uuid_version, a strongly typed enum representing the version.
Making those two properties part of the type rather than a runtime flag is what gives the library its usefulness. A version check becomes a comparison against an enumerator instead of an integer, and the code says what it means:
uuid const id = uuids::uuid_system_generator{}();
assert(!id.is_nil());
assert(id.version() == uuids::uuid_version::random_number_based);
assert(id.variant() == uuids::uuid_variant::rfc);The uuid type also exposes its bytes, as_bytes(), which the examples check for a size of 16, and a nil predicate. The canonical textual representation is handled separately, by to_string(), which creates a string and also has an overload taking an output iterator that writes the same characters to a caller-provided buffer without allocating, which is the overload to reach for in a hot path or when writing into a fixed buffer.
For output, there is an operator<< that writes the canonical text form to an output stream, and a std::formatter<uuid> specialization so a UUID can be formatted with std::format() when the standard library provides the format header. Both give the same canonical representation, so a UUID printed by the stream operator and a UUID printed through std::format() look identical.
Five generators, two of them behind macros
The generators cover the UUID versions a real system actually uses.
basic_uuid_random_generator is a function object that generates version 4 UUIDs using a pseudo-random number generator engine, and it is templated on the engine type. uuid_random_generator is that same generator using the Mersenne Twister engine, in other words basic_uuid_random_generator<std::mt19937>. The example shows the full seeding path, filling a std::seed_seq from std::random_device before constructing the engine, and then passing the engine to the generator:
std::random_device rd;
auto seed_data = std::array<int, std::mt19937::state_size> {};
std::generate(std::begin(seed_data), std::end(seed_data), std::ref(rd));
std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
std::mt19937 generator(seq);
uuids::uuid_random_generator gen{generator};The same pattern works with a different engine, for example std::ranlux48_base, by instantiating basic_uuid_random_generator<std::ranlux48_base> instead.
The other two generators are not always compiled in. uuid_system_generator generates new UUIDs using operating system resources, specifically CoCreateGuid on Windows, uuid_generate on Linux and CFUUIDCreate on Mac. It is available only if the UUID_SYSTEM_GENERATOR macro is defined, and the README notes that it is not part of the standard proposal. uuid_time_generator generates time-based UUIDs, is available only if the UUID_TIME_GENERATOR macro is defined, and is labelled experimental with the warning that it should not be used in any production code.
That warning is worth taking literally. The time-based generator is the one version of UUID that encodes both a timestamp and a node identity, and it is exactly the one with the worst reputation in practice, so the library has kept it behind a macro and out of production use.
Name-based UUIDs and the namespace constants
uuid_name_generator produces version 5, name-based UUIDs by hashing with SHA1. The generator is constructed from a namespace UUID, and the name to hash is the argument of the call operator:
uuids::uuid_name_generator gen(uuids::uuid_namespace_dns);
uuid const id = gen("john");
assert(!id.is_nil());
assert(id.version() == uuids::uuid_version::name_based_sha1);
assert(id.variant() == uuids::uuid_variant::rfc);Four namespace constants are predefined for the common cases: uuid_namespace_dns for a fully-qualified domain name, uuid_namespace_url for a URL, uuid_namespace_oid for an ISO OID and uuid_namespace_x500 for an X.500 distinguished name in DER or text form.
Any UUID can serve as a namespace, so an application-specific one can be used instead. The parsing rules are precise and worth knowing. Only the namespace is parsed with from_string(), never the name, and the result has to be checked because parsing fails for anything that is not a UUID:
auto ns = uuids::uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43");
assert(ns.has_value());
uuids::uuid_name_generator gen(ns.value());
uuid const id = gen("john");The same name under the same namespace always yields the same UUID, which is the property that makes name-based identifiers useful for deduplication and for deriving an identifier from a natural key. It also means the namespace choice is part of your data model, because changing it changes every identifier derived under it.
The small interop pieces that decide whether it fits
Two standard library specialisations decide whether a uuid can live inside ordinary containers. There is a std::swap<> specialisation for uuid, and a std::hash<> specialisation, which the README notes is necessary for storing UUIDs in unordered associative containers such as std::unordered_set.
That hash specialisation is the detail most likely to matter to a codebase considering the library. Without it, every UUID in an unordered container needs a custom hasher or a string conversion, and the string conversion allocates. With it, the type behaves like the value types the standard library already handles.
Comparison is provided by operator== and operator!= for equality, and by operator< for ordering. The README is honest about the ordering operator: although the operation does not make much logical sense, it is necessary in order to store UUIDs in a std::set. That is a fair description of why it exists, and a reminder that the ordering is a technical requirement rather than a meaningful ranking of identifiers.
The library also provides a nil UUID through default construction, which the first example checks with assert(empty.is_nil()).
Building the library and tracking a proposal that moves
The library is a single header, so the simplest use is to take include/uuid.h and put it on your include path. For a CMake-based build, the repository carries a CMakeLists.txt at the root plus cmake/ and include/ directories, and how_to_build.md in the root is the place to look for the build instructions, alongside the test/ directory and the catch/ directory for the Catch2-based tests. There is also a gsl/ directory and a CI workflow badge in the README.
The tests matter more than they would for a library this small, because the guarantees being checked are exacting. Every generator has to produce a value with sixteen bytes, a nil check has to hold, and the version and variant enums have to come back as the documentation claims for each generator. A test suite that asserts those properties on every platform is what makes the library usable on Windows, Linux and Mac rather than on the author's machine only.
The other thing to plan for is the moving target. The implementation follows proposal P0959, and the README says the implementation will reflect the proposal's evolution as the committee and community give feedback. If your code depends on the exact API shape, an upstream update can change it. That is a reasonable trade for a library this focused, as long as you pin your version and watch the proposal's revision history, which the README points to for the history of changes.
For a project that only needs a random identifier and already links a platform library with uuid_generate, CoCreateGuid or CFUUIDCreate, the system generator is the smaller dependency. stduuid earns its place when you want the typed version and variant, the name-based generator, the hash specialisation and the canonical formatting in one header with no platform branches of your own.
Editorial conclusion
stduuid suits a C++17 codebase that wants typed UUIDs with version and variant checks, unordered container support and stream formatting, without taking a dependency on a heavier framework. It does not suit a project that needs the time-based generator, which the library marks experimental and warns against in production, or one that assumes a finalised standard API. Before you adopt it, read P0959.md, because the implementation follows the proposal and will change as the committee and community move it, and confirm your build can pick up the include/ header and the CMake targets described in how_to_build.md.
Frequently asked questions
What is stduuid and why does it use the uuids namespace instead of std?
stduuid is a C++17 cross-platform single-header library for UUIDs. The specification puts the uuid library in the std namespace, but this implementation uses uuids in order to be usable without violating the restrictions imposed on the std namespace.
Which UUID versions can stduuid generate?
The random generators produce version 4 UUIDs, uuid_name_generator produces version 5 name-based UUIDs using SHA1, and uuid_system_generator uses operating system resources such as CoCreateGuid on Windows, uuid_generate on Linux and CFUUIDCreate on Mac, but only when the UUID_SYSTEM_GENERATOR macro is defined. The time-based generator needs the UUID_TIME_GENERATOR macro and is marked experimental.
How do I create a name-based UUID with stduuid?
Construct a uuid_name_generator with a namespace UUID and call it with the name to hash, for example uuids::uuid_name_generator gen(uuids::uuid_namespace_dns); then uuid const id = gen("john"). Only the namespace is parsed with from_string(), and the parse result must be checked because it fails for anything that is not a UUID.
Can I use stduuid with std::unordered_set and std::format?
Yes. The library provides a std::hash<> specialisation for uuid, which the README says is necessary for storing UUIDs in unordered associative containers such as std::unordered_set, and a std::formatter<uuid> specialisation for use with std::format() when the standard library provides the format header.
Is stduuid based on a finalised C++ standard?
No. The library is an implementation of proposal P0959, and the README states that as the proposal evolves based on standard committee and community feedback, the implementation will reflect those changes. The licence field on the repository says MIT.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mariusbancila-stduuid)Was this useful?