Created
April 16, 2025 14:11
-
-
Save dhilst/1a63ad17664b771001b3eb9068106891 to your computer and use it in GitHub Desktop.
Type safe primitives in C++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Phantom is a phatom type. It means that it is not used | |
// during runtime, but play type safety roles during compile | |
// time. | |
template <typename Phantom> | |
struct StrongId { | |
int id; | |
// Conversion int -> StrongId<T> must to be explicit | |
explicit StrongId(auto id_) : id(id_) {} | |
// Conversion StrongId<T> -> int is implicit | |
constexpr operator auto() const { return id; } | |
}; | |
// We can use the type being defined to define strong primitives | |
// like int. In fact we can generalize StrongId to accept another | |
// type parameter and use any time instead of 'int', but I'm not | |
// doing this here for sake of simplicity. | |
// | |
// Here we use the struct being defined to define strongly typed | |
// ids. These ids CANNOT be swapped by accident because the program | |
// is ill-typed if you do so. | |
struct Store { StrongId<Store> id; }; | |
struct Product { StrongId<Product> id; }; | |
// Even if 'Product' is an incomplete type | |
// inside its own definition, this does not matter much because the | |
// type is not used at runtime, so its size does not matter in this case. | |
// Note: Incomplete type is a struct or class that is declared but not | |
// defined, for example `struct foo;` declares a struct but does not | |
// defines it. Because is not defined its size is unknown, and because of | |
// that there are things that you cannot do with it, like allocating it, | |
// (how much bytes would you allocate?), but all this is off-topic | |
// Back to phantom types, this just an example function accepting an int | |
void findById(int id) {} | |
// This function cannot be called with a StrongId<Store> by accident | |
void findProductById(StrongId<Product> productId) { | |
// operator int() converts it to int automatically | |
findById(productId); | |
} | |
void testStrongId(Product product, Store store) { | |
// You can't pass a simple int to it, it need to be wrapped | |
findProductById(1); // Type error: No matching function for call to 'findProductById' | |
findProductById(product.id); // ok | |
findProductById(store.id); // Type error: No matching function for call to 'findProductById' | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment