# Frozen: compile-time containers for C++14

> Frozen is a header-only C++14 library for immutable sets, maps, fixed-capacity maps, and compile-time search tables. It suits programs that want constant-time lookup without runtime container initialization, but its perfect-hash layout increases compile-time work and requires careful capacity planning.

**serge-sans-paille/frozen** — a header-only, constexpr alternative to gperf for C++14 users

- Repository: https://github.com/serge-sans-paille/frozen
- Stars: 1,581 · Forks: 121
- Language: C++
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/serge-sans-paille-frozen

## Frozen's compile-time container model

Frozen addresses a specific C++ engineering problem: constructing read-only lookup structures without paying for the ordinary initialization performed by `std::set`, `std::unordered_set`, `std::map`, or `std::unordered_map`. It targets C++14 programs that use `constexpr` or `constinit` for data known before runtime. This includes embedded configuration, enum-to-string mappings, command names, HTML entity tables, and other fixed lookup tables.

The library provides immutable `constexpr` versions of the four standard associative containers listed in the README. It also provides fixed-capacity `constinit` versions of `map` and `unordered_map`, where keys are selected at compile time but mapped values can be changed at runtime. The distinction is narrower than a general mutable hash map. Keys never change after initialization.

Frozen also supplies a zero-cost initialization version of `std::search` for fixed needles, using either Boyer-Moore or Knuth-Morris-Pratt searching. That is a separate facility from the containers and should not be confused with a general replacement for runtime substring search.

The intended users are C++ developers who accept compile-time work in exchange for immutable data and faster lookup. It is a poor fit when the key set is only known at runtime, when keys must be inserted after startup, or when compiler evaluation limits become a practical constraint.

## Perfect hashing and fixed capacity

Frozen's `unordered_*` containers use a perfect hash, meaning the documented design guarantees no hash collision and uses extra storage linear in the number of keys. Based on that design, the README states that lookup remains `O(1)`. The structure is fixed-capacity, so the maximum number of entries is part of the container type in examples such as `frozen::unordered_map<frozen::string, int, 2>`.

This design gives Frozen a specific performance and storage trade-off. Callers exchange flexibility for a lookup structure generated during constant evaluation. They cannot append keys later. They also cannot treat the unordered container as a drop-in mutable equivalent of `std::unordered_map`.

The input hash still matters. Frozen uses the supplied hasher while constructing its perfect mapping, and the documentation says a poor hash makes searching for a collision-free layout take longer. Poor input hashing therefore affects compile time rather than turning the final lookup into an ordinary collision-chain search. The documented extension interface is the `frozen::elsa` structure:

```cpp
template <class T> struct elsa {
  // in case of collisions, different seeds are tried
  constexpr std::size_t operator()(T const &value, std::size_t seed) const;
};
```

The recommended quality is low collision probability for different values across random seeds. The README also permits specialization of the hash, equality comparator, and ordered-container comparison functions. Those extension points make unusual key types possible, but they also move part of the data-model design work to the adopting project.

## Installing Frozen without a package manager

Frozen has no documented package-manager installation and no GitHub release is listed. The README says to copy the `include/frozen` directory into the project's include path, or to use the repository's CMake installation. For a direct header-only setup, copying `include/frozen` and adding its parent directory with `-I` is the documented approach.

The repository also exposes a CMake workflow. The commands below create a build directory, configure a Release build, and install the project:

```bash
mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=Release ..
make install
```

The README states that this installation writes configuration files under `/usr/local/share`, where CMake consumers can locate them through `find_package`. A source checkout is therefore the starting point in either documented path. 

For the header-only route, include the specific container header rather than a single aggregate header. The examples use `<frozen/set.h>`, `<frozen/unordered_map.h>`, and `<frozen/string.h>`. This keeps dependencies visible and limits each translation unit to the facility it uses.

## Creating and querying a frozen set

A first use can be a small integer set whose membership is known at compile time. The container's second template argument is its capacity, so `4` sets the compile-time storage limit. With constant-expression arguments, `count` can participate in `constexpr` evaluation.

Compile the example with `-std=c++14`, as specified in the README:

```cpp
#include <frozen/set.h>

constexpr frozen::set<int, 4> some_ints = {1,2,3,5};

constexpr bool letitgo = some_ints.count(8);

extern int n;
bool letitgoooooo = some_ints.count(n);
```

`some_ints` contains four values, so the initializer is complete at its declared capacity. `letitgo` records the constant-expression result of looking up `8`, while `letitgoooooo` performs a runtime lookup using the externally defined `n`. This demonstrates the useful boundary: the data structure can be created before runtime, while its query operation can still consume a runtime value.

Frozen also offers a `make_set` helper for less repetitive initialization:

```cpp
#include <frozen/set.h>

constexpr auto some_ints = frozen::make_set<int>({1,2,3,5});
```

The README says similar `make_X` functions exist for all Frozen containers. It does not document a runtime builder API, automatic resizing, or insertion methods for immutable containers.

## Mapping Frozen strings and modifying mapped values

String lookup is supported through `<frozen/string.h>`. The following `unordered_map` declares a capacity of two and is initialized with string keys mapped to integers. Because the variable is `constexpr`, both its keys and values are immutable:

```cpp
#include <frozen/unordered_map.h>
#include <frozen/string.h>

constexpr frozen::unordered_map<frozen::string, int, 2> olaf = {
    {"19", 19},
    {"31", 31},
};
constexpr auto val = olaf.at("19");
```

Frozen's fixed-capacity maps have a different mode when they are initialized with `constinit`. In that case, the keys remain immutable, but the mapped values can be updated at runtime. The README uses voice-actor names as the example:

```cpp
#include <frozen/unordered_map.h>
#include <frozen/string.h>

static constinit frozen::unordered_map<frozen::string, frozen::string, 2> voice = {
    {"Anna", "???"},
    {"Elsa", "???"}
};

int main() {
    voice.at("Anna") = "Kristen";
    voice.at("Elsa") = "Idina";
}
```

This is useful when the key universe is fixed but values need runtime state. It does not provide a way to replace `"Anna"` with a different key. The capacity is also checked as part of the type, so increasing or decreasing the number of initial keys requires changing the container declaration.

## Compiler limits and exception behavior

Frozen requires a C++14 compiler. The README names Clang 5 as a suitable choice, says GCC 6 compiles correctly but lags behind in `constexpr` compilation time, and states that Visual Studio 2017 works correctly. GCC 5 is explicitly unsupported. An older compatibility branch exists for GCC 5, but the README does not describe that branch as the normal installation target.

Large containers and unsuitable hashes can exhaust Clang's constant-evaluation steps. The documented diagnostic contains the text `constexpr evaluation hit maximum step limit; possible infinite loop?`. The README gives two responses: increase the threshold for a genuinely large container, or replace a hash that is ineffective for the input data. Its example threshold is:

```bash
-fconstexpr-steps=1000000000
```

Increasing the limit can make compilation proceed further, but it does not improve the underlying hash. A custom `elsa` specialization is the documented route when the default hash does not suit the keys.

Exception behavior is configurable. Frozen may throw from STL-compatible operations such as `map::at`, but when exceptions are disabled or `FROZEN_NO_EXCEPTIONS` is defined, those paths use `std::abort`. Projects should therefore not assume that a failed lookup always returns through an exception. The documentation describes possible eventual exception behavior, not a complete compatibility contract for every standard-library error path.

## Maintenance, licensing, and adoption boundaries

The repository is not archived, and its last push was on 2026-10-09. The README nevertheless places Frozen in maintenance mode. It calls `master` the default stable version and says review latency should be expected for cleanup, performance, or modernization commits. There are no GitHub releases listed, so `master` is the documented stable line rather than a numbered release artifact.

Frozen is licensed under Apache-2.0. That identifies the repository's declared licensing model, but this review does not provide legal advice about how an organization should apply the license. Upgrade work also has a project-specific shape: adoption depends on copying headers or consuming a CMake installation rather than selecting a semantic version from the documented release history.

Compared with ordinary standard-library associative containers, Frozen moves setup into compile-time evaluation and fixes unordered capacity in the type. Compared with a hand-written switch or table, it offers a C++14 container interface, custom comparators and hashes, and `constexpr` compatibility. Compared with a conventional mutable hash map, it cannot add or replace keys at runtime. The repository's `tests/`, `benchmarks/`, and `examples/` directories indicate that tests, benchmark code, and usage examples exist, but the README's claims should not be read as performance measurements for a particular adopting system.

## Conclusion

Adopt Frozen for static lookup tables whose contents can be fixed at compile time and whose keys can use a suitable hash function. Do not choose it for containers that need runtime insertion or key mutation, or when build time and binary size are tighter constraints than lookup cost. Verify first that the project structure, target compiler, data size, and custom hash fit the documented C++14 and `constexpr` requirements.

## FAQ

### What is Frozen used for in C++14?

Frozen provides immutable sets and maps, fixed-capacity maps with mutable values, and compile-time search tables. It is intended for static lookup data initialized with `constexpr` or `constinit`.

### How do I install Frozen?

The README says to copy the `include/frozen` directory and add its parent path with `-I`. It also documents a CMake build followed by `make install`.

### Can Frozen containers insert or replace keys at runtime?

No. Once initialized, Frozen container keys cannot be updated. In the `constinit` fixed-capacity map mode, mapped values can change, but the keys remain immutable.

## Sources

- [Issues](https://github.com/serge-sans-paille/frozen/issues)
- [License: Apache-2.0](https://github.com/serge-sans-paille/frozen/blob/master/LICENSE)
- [README](https://github.com/serge-sans-paille/frozen/blob/master/README.md)
- [serge-sans-paille/frozen on GitHub](https://github.com/serge-sans-paille/frozen)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/serge-sans-paille-frozen
