Prove uniqueness of C enums at compile-time through generated recursive Vandermonde determinant macros.
uniqenum is a C/C++ preprocessor macro library to enforce uniqueness of enumeration values at compile-time, using static assertions.
The code is written using a meta-programming generator written in TypeScript/Node.js.
For quick usage, see the samples directory for pre-generated headers.
``text`
uniqenum
...
enum-name and typedef-name can be blank.
Usage:
`c`
typedef uniqenum5(E1e,A1,=1,B1,=2,C1,=3,D1,=4,E1,=5,E1t); // typedef enum E1e{} E1t named + typedef
typedef uniqenum5(,A2,=1,B2,=2,C2,=3,D2,=4,E2,=5,E2t); // typedef enum{} E2t typedef
uniqenum5(E3e,A3,=1,B3,=2,C3,=3,D3,=4,E3,=5,); // enum E3e{} named
uniqenum5(,A4,=1,B4,=2,C4,=3,D4,=4,E4,=5,); // enum{} anonymous
Before including areuniq or uniquenum headers, you can define configuration macros to alter assertion behavior.
Integer. Definies the assertion mode.
UNIQENUM_ASSERT value|Name|Description|Pros|Cons
-|-|-|-|-
0|none|Assertions disabled|Faster compilation|No verification of uniqueness
1 or undefined|once|Assert once all enumerators together|Verifies uniqueness
Acceptable compilation speed|Duplicate values can be hard to debug for large enums
Complex static assertion expressions for large enums
2|all|Assert all pairs|Verifies uniqueness
Shows duplicated enumerators|Slower compilation
Sometimes multiple assertions per pair under this implementation
> [!TIP]
> You can bind UNIQENUM_ASSERT to NDEBUG to map Uniqenum to existing debug/release configurations
Function-like macro. Customizes the assertion to execute in assert-all mode. Syntax:
`c`
#define UNIQENUM_ASSERT_ALL(expr,a,b) _Static_assert((expr), "duplicate enum values: "#a" and "#b)
- expr is the boolean expression to assert;
- a is the first enumerator;
- b is the second enumerator.
Function-like macro. Customizes the assertion to execute in assert-once mode. Syntax:
`c`
#define UNIQENUM_ASSERT_ONCE(expr,name,type) _Static_assert((expr),"enum has duplicate values: "#name" "#type)
- expr is the boolean expression to assert;
- name is the name of the enumeration;
- type is the typedef name of the enumeration.
name and type may be empty if the enumeration doesn't declare a name/typedef.
The CLI and the API are shipped as the same npm package. Install globally for command-line usage, or add it to your project for programmatic generation.
`bash`
npm install -g uniqenum # CLI
npm install -D uniqenum # API within a project
`sh`
uniqenum
- Nstart / Nend define the inclusive range of arity (N) to generate. Nend defaults to Nstart, and accepts inf/infinity for open-ended streams.areuniq
- By default, both and uniqenum families are emitted. Use --areuniq or --uniqenum to limit the output.-o, --out-file
- defines the output path.-d, --asdir
- shreds the output into multiple headers (with automatic include tries) honoring --max-size per file.--max-size
- When neither is passed, output goes to stdout.
- sets the byte budget when streaming to stdout or a single file (required for unbounded ranges). The directory mode defaults to 256 KiB if you omit it.-g, --guard
- controls the include strategy for flat outputs; directory mode stores guards inside each shard.
Examples:
`sh1. Stream both families for N=1..64 to stdout
uniqenum 1 64
When both families are selected, the CLI interleaves them (
areuniqN immediately followed by uniqenumN) so every uniqenum macro has a matching dependency in the same file. For finite ranges the generator will emit the whole interval, but will warn whenever the final size exceeds the configured --max-size by reporting the overage in bytes.Generator API usage
The
generate function exposes the exact capabilities of the CLI plus additional formatting controls:`ts
import { generate } from 'uniqenum';generate({
range: { start: 1, end: Infinity }, // single N or inclusive range
macros: ['areuniq', 'uniqenum'], // pick the families to emit
includeGuards: 'classic',
names: { // optional macro name templates
areuniq: ['areuniq', { ref: 'n' }],
uniqenum: ['uniqenum', { ref: 'n' }],
},
output: {
type: 'file', // 'stdout' | 'file' | 'directory'
path: 'include/uniqenum.h',
maxBytes: 256 * 1024, // required for unbounded ranges
},
});
`Output targets:
-
stdout: streams directly to the current process output. Accepts maxBytes and includeGuards.
- file: writes a single header. Directories are created automatically. Requires maxBytes for infinite ranges.
- directory: splits output into multiple files with automatic include guards, trie-based layout, and dependency includes. Use maxFileSize to limit each shard.All options are fully typed (see
src/index.ts) so you get auto-complete inside TypeScript projects. The Samples generators (samples/gen-samples.ts and samples/repository/gen-repository.ts`) are minimal scripts that exercise the API to produce the pre-generated headers committed in this repo.