Skip to content

gershnik/modern-uuid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

modern-uuid

Language Standard License Tests

A modern, no-dependencies, portable C++ library for manipulating UUIDs, ULIDs, NanoIDs and (currently experimental) Cuid2.

Features

  • UUID: Implements newer RFC 9562 (which supersedes 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 external textual format identical to the JavaScript library and 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 as a port of the JavaScript library.
    • In particular, NanoID objects are internally stored and manipulated in packed binary format rather than strings resulting in much better memory efficiency
  • Self-contained with no dependencies beyond C++ standard library.
  • Works on Mac, Linux, Windows, BSD, Wasm, and even Illumos. Might even work on some embedded systems given a suitable compiler and a 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 an obvious exception of XXID generation and iostream I/O) are constexpr and 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 "safe" constructs only in public interface (no raw pointers and such).
  • Properly handles fork with no exec on Unix systems. XXIDs generated by the child process will not collide with parent's.

See also differences from other UUID libraries.

Usage

A quick intro to the library is given below. For more details see:

UUID

#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_v2);
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 iostream 

//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 std::ios_base::uppercase stream flag
ostr << std::uppercase << uuid("7d444840-9dc0-11d1-b245-5ffdce74fad2");
assert(ostr.str() == "7D444840-9DC0-11D1-B245-5FFDCE74FAD2");

ULID

#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 iostream 

//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 std::ios_base::uppercase stream flag
ostr << std::uppercase << ulid("01bx5zzkbkactav9wevgemmvry");
assert(ostr.str() == "01BX5ZZKBKACTAV9WEVGEMMVRY");

NanoID

#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 != u1);
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 iostream 

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 ID might be different from 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 default nanoid

Cuid2

#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 cuid2 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 iostream 

//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 std::ios_base::uppercase stream flag
ostr << std::uppercase << cuid2("kgiupd0rsg67b97553xdrg2f");
assert(ostr.str() == "KGIUPD0RSG67B97553XDRG2F");

Building/Integrating

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
)