A modern, no-dependencies, portable C++ library for manipulating UUIDs, ULIDs, NanoIDs and Cuid2s.
-
UUID: Implements the newer RFC 9562 (which supersedes the older RFC 4122). Supports generation of UUID variants 1, 3, 5, 6 and 7.
-
ULID: Implements the canonical spec.
-
NanoID: Since there is no formal spec, this library implements an external textual format identical to the JavaScript library and a generation algorithm fully equivalent to it. It also supports custom alphabets and sizes with the same semantics. The implementation is done from first principles - it is not a port of the JavaScript library.
- In particular, NanoID objects are internally stored and manipulated in a packed binary format rather than strings, resulting in much better memory efficiency.
-
Self-contained with no dependencies beyond the C++ standard library.
-
Works on Mac, Linux, Windows, BSD, Wasm, Illumos and even Haiku OS. Might even work on some embedded systems given a suitable compiler and standard library support.
-
Requires C++20 but does not require a very recent compiler (GCC is supported from version 10 and Clang from version 13).
-
Most operations (with the obvious exceptions of XXID generation and iostream I/O) are
constexprand can be done at compile time. Notably, this enables:- Natural syntax for compile-time XXID literals.
- Using XXIDs as template parameters and in other compile-time contexts.
-
Supports
std::format(if available) for formatting and parsing in addition to iostreams. -
Does not rely on C++ exceptions and can be used with C++ exceptions disabled.
-
Uses only "safe" constructs in the public interface (no raw pointers and such).
-
Properly handles
forkwith noexecon Unix systems. XXIDs generated by the child process will not collide with the parent's.
See also differences from other UUID libraries.
A quick intro to the library is given below. For more details, see:
#include <modern-uuid/uuid.h>
using namespace muuid;
//this is a compile-time UUID literal
constexpr uuid u1("e53d37db-e4e0-484f-996f-3ab1d4701abc");
//if you want to, you can use uuid as a template parameter
template<uuid U> class some_class {...};
some_class<uuid("bc961bfb-b006-42f4-93ae-206f02658810")> some_object;
//you can generate all non-proprietary versions of UUID from RFC 9562:
uuid u_v1 = uuid::generate_time_based();
uuid u_v3 = uuid::generate_md5(uuid::namespaces::dns, "www.widgets.com");
uuid u_v4 = uuid::generate_random();
uuid u_v5 = uuid::generate_sha1(uuid::namespaces::dns, "www.widgets.com");
uuid u_v6 = uuid::generate_reordered_time_based();
uuid u_v7 = uuid::generate_unix_time_based();
//for non-literal strings, you can parse uuids from strings using uuid::from_chars
//the argument to from_chars can be anything convertible to std::span<char>
//the call is constexpr
std::string some_uuid_str = "7D444840-9DC0-11D1-B245-5FFDCE74FAD2";
std::optional<uuid> maybe_uuid = uuid::from_chars(some_uuid_str);
if (maybe_uuid) {
uuid parsed = *maybe_uuid;
}
//uuid objects can be compared in every possible way
assert(u_v1 > uuid());
assert(u_v1 != u_v3);
std::strong_ordering res = (u_v6 <=> u_v7);
//etc.
//uuid objects can be hashed
std::unordered_map<uuid, transaction> transaction_map;
//they can be formatted. u and l stand for uppercase and lowercase
std::string str = std::format("{}", u1);
assert(str == "e53d37db-e4e0-484f-996f-3ab1d4701abc");
str = std::format("{:u}", u1);
assert(str == "E53D37DB-E4E0-484F-996F-3AB1D4701ABC");
str = std::format("{:l}", u1);
assert(str == "e53d37db-e4e0-484f-996f-3ab1d4701abc");
//uuids can be read/written from/to iostreams
//when reading, case doesn't matter
std::istringstream istr("bc961bfb-b006-42f4-93ae-206f02658810");
uuid uuidr;
istr >> uuidr;
assert(uuidr == uuid("bc961bfb-b006-42f4-93ae-206f02658810"));
std::ostringstream ostr;
ostr << uuid("bc961bfb-b006-42f4-93ae-206f02658810");
assert(ostr.str() == "bc961bfb-b006-42f4-93ae-206f02658810");
ostr.str("");
//writing respects the std::ios_base::uppercase stream flag
ostr << std::uppercase << uuid("7d444840-9dc0-11d1-b245-5ffdce74fad2");
assert(ostr.str() == "7D444840-9DC0-11D1-B245-5FFDCE74FAD2");#include <modern-uuid/ulid.h> //note the 'l'
using namespace muuid;
//this is a compile-time ULID literal
constexpr ulid u1("01BX5ZZKBKACTAV9WEVGEMMVRY");
//if you want to, you can use ulid as a template parameter
template<ulid U1> class some_class {...};
some_class<ulid("01BX5ZZKBKACTAV9WEVGEMMVRY")> some_object;
ulid u2 = ulid::generate();
//for non-literal strings, you can parse ulids from strings using ulid::from_chars
//the argument to from_chars can be anything convertible to std::span<char>
//the call is constexpr
std::string some_ulid_str = "01bx5zzkbkactav9wevgemmvry";
std::optional<ulid> maybe_ulid = ulid::from_chars(some_ulid_str);
if (maybe_ulid) {
ulid parsed = *maybe_ulid;
}
//ulid objects can be compared in every possible way
assert(u2 > ulid());
assert(u2 != u1);
std::strong_ordering res = (u2 <=> u1);
//etc.
//ulid objects can be hashed
std::unordered_map<ulid, transaction> transaction_map;
//they can be formatted. u and l stand for uppercase and lowercase
std::string str = std::format("{}", u1);
assert(str == "01bx5zzkbkactav9wevgemmvry");
str = std::format("{:u}", u1);
assert(str == "01BX5ZZKBKACTAV9WEVGEMMVRY");
str = std::format("{:l}", u1);
assert(str == "01bx5zzkbkactav9wevgemmvry");
//ulids can be read/written from/to iostreams
//when reading, case doesn't matter
std::istringstream istr("01bx5zzkbkactav9wevgemmvry");
ulid ur;
istr >> ur;
assert(ur == ulid("01bx5zzkbkactav9wevgemmvry"));
std::ostringstream ostr;
ostr << ulid("01bx5zzkbkactav9wevgemmvry");
assert(ostr.str() == "01bx5zzkbkactav9wevgemmvry");
ostr.str("");
//writing respects the std::ios_base::uppercase stream flag
ostr << std::uppercase << ulid("01bx5zzkbkactav9wevgemmvry");
assert(ostr.str() == "01BX5ZZKBKACTAV9WEVGEMMVRY");#include <modern-uuid/nanoid.h>
using namespace muuid;
//this is a compile-time NanoID literal
constexpr nanoid n1("V1StGXR8_Z5jdHi6B-myT");
//if you want to, you can use nanoid as a template parameter
template<nanoid N> class some_class {...};
some_class<nanoid("V1StGXR8_Z5jdHi6B-myT")> some_object;
//generate a NanoID:
nanoid ng = nanoid::generate();
//for non-literal strings, you can parse nanoids from strings using nanoid::from_chars
//the argument to from_chars can be anything convertible to std::span<char, any extent>
//the call is constexpr
std::string some_nanoid_str = "Uakgb_J5m9g-0JDMbcJqL";
std::optional<nanoid> maybe_nanoid = nanoid::from_chars(some_nanoid_str);
if (maybe_nanoid) {
nanoid parsed = *maybe_nanoid;
}
//nanoid objects can be compared in every possible way
assert(ng > nanoid());
assert(ng != n1);
std::strong_ordering res = (ng <=> n1);
//etc.
//nanoid objects can be hashed
std::unordered_map<nanoid, transaction> transaction_map;
//they can be formatted.
std::string str = std::format("{}", n1);
assert(str == "V1StGXR8_Z5jdHi6B-myT");
//nanoids can be read/written from/to iostreams
std::istringstream istr("V1StGXR8_Z5jdHi6B-myT");
nanoid nr;
istr >> nr;
assert(nr == nanoid("V1StGXR8_Z5jdHi6B-myT"));
std::ostringstream ostr;
ostr << nanoid("V1StGXR8_Z5jdHi6B-myT");
assert(ostr.str() == "V1StGXR8_Z5jdHi6B-myT");
//You can define different nanoid types that use custom alphabets and sizes
MUUID_DECLARE_NANOID_ALPHABET(my_alphabet, "1234567890abcdef");
using my_id = basic_nanoid<my_alphabet, 10>;
//Note that the size of such an ID might be different from the default nanoid
static_assert(sizeof(my_id) == 5);
constexpr my_id mid1("4f90d13a42");
my_id mid2 = my_id::generate();
//etc.
//all other nanoid operations are the same (with different string/buffer sizes)
//as for the default nanoid#include <modern-uuid/cuid2.h>
using namespace muuid;
//this is a compile-time Cuid2 literal
constexpr cuid2 c1("kgiupd0rsg67b97553xdrg2f");
//if you want to, you can use cuid2 as a template parameter
template<cuid2 C> class some_class {...};
some_class<cuid2("kgiupd0rsg67b97553xdrg2f")> some_object;
//generate a Cuid2:
cuid2 cg = cuid2::generate();
//for non-literal strings, you can parse cuid2s from strings using cuid2::from_chars
//the argument to from_chars can be anything convertible to std::span<char, any extent>
//the call is constexpr
std::string some_cuid2_str = "a96dz76vcu55q6aorin51oqo";
std::optional<cuid2> maybe_cuid2 = cuid2::from_chars(some_cuid2_str);
if (maybe_cuid2) {
cuid2 parsed = *maybe_cuid2;
}
//cuid2 objects can be compared in every possible way
assert(cg > cuid2());
assert(cg != c1);
std::strong_ordering res = (cg <=> c1);
//etc.
//cuid2 objects can be hashed
std::unordered_map<cuid2, transaction> transaction_map;
//they can be formatted. u and l stand for uppercase and lowercase
std::string str = std::format("{}", c1);
assert(str == "kgiupd0rsg67b97553xdrg2f");
str = std::format("{:u}", c1);
assert(str == "KGIUPD0RSG67B97553XDRG2F");
str = std::format("{:l}", c1);
assert(str == "kgiupd0rsg67b97553xdrg2f");
//cuid2s can be read/written from/to iostreams
//when reading, case doesn't matter
std::istringstream istr("kgiupd0rsg67b97553xdrg2f");
cuid2 cr;
istr >> cr;
assert(cr == cuid2("kgiupd0rsg67b97553xdrg2f"));
std::ostringstream ostr;
ostr << cuid2("kgiupd0rsg67b97553xdrg2f");
assert(ostr.str() == "kgiupd0rsg67b97553xdrg2f");
ostr.str("");
//writing respects the std::ios_base::uppercase stream flag
ostr << std::uppercase << cuid2("kgiupd0rsg67b97553xdrg2f");
assert(ostr.str() == "KGIUPD0RSG67B97553XDRG2F");The quickest CMake method is given below. For more details and other methods, see Integration Guide.
include(FetchContent)
FetchContent_Declare(modern-uuid
GIT_REPOSITORY git@github.com:gershnik/modern-uuid.git
GIT_TAG <desired tag like v1.2 or a sha>
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(modern-uuid)
...
target_link_libraries(mytarget
PRIVATE
modern-uuid::modern-uuid
)