TTS is header-only, and it targets test suites - which typically means dozens or hundreds of small translation units, each #include <tts/tts.hpp>-ing the whole library on its own. There is no separately-compiled .o to amortize a header's parsing cost over: every header TTS pulls in is paid, in full, by every test file, in every project that uses it, on every build. This project's own CI matrix alone rebuilds the test suite across more than a dozen compiler/OS/standard-library combinations per pull request - a single heavy header added to a commonly-included file is not a one-time cost, it is a cost multiplied by every one of those builds, forever.
This is why TTS reimplements small, purpose-built pieces of what it needs (a minimal string type, a monotonic clock, a move-only file wrapper, ...) instead of reaching for the equivalent standard library facility: the value proposition of a tiny test system depends on it staying cheap to include.
Intuition about which headers are "heavy" is unreliable - some innocuous-looking ones aren't free, and some that look expensive on paper measure as free in practice. Only measuring settles it. The numbers below come from timing a minimal TTS_MAIN translation unit with and without each header added, g++ 13 and clang++ 18, -std=c++20, best-of-8 runs (see Before you add a header for the exact method):
| Header | Added cost (g++) | Added cost (clang++) | Avoided via |
|---|---|---|---|
| <chrono> | +460ms (~4x) | +680ms (~6x) | tools/clock.hpp's now_ns(), built on clock_gettime/QueryPerformanceCounter |
| <memory> | +170ms (~2x) | +210ms (~2.5x) | tools/file.hpp's file_guard, a hand-rolled move-only RAII wrapper instead of std::unique_ptr |
| <sstream> | +140ms | +190ms | tools/text.hpp builds strings via malloc/snprintf |
| <iostream> | +140ms | +170ms | output goes through FILE*/fputs, not std::cout |
| <functional> | +100ms | +100ms | never - tools/callable.hpp's type-erased callable is what every single TTS_CASE body is wrapped in, the hottest path in the whole library, so this one doesn't get a "genuinely needed" exception |
| <string> | +90ms | +110ms | tts::text is TTS's own minimal string type |
| <unordered_map> | +60ms | +60ms | avoided unless genuinely needed |
| <vector> | +50ms | +60ms | tools/buffer.hpp's tts::buffer, a minimal malloc-based dynamic array |
| <map> | +50ms | +50ms | avoided unless genuinely needed |
| <array> | ~0ms (noise) | ~0ms (noise) | fine to use - see below |
TTS doesn't need the two headers below for anything today, so there's no "avoided via" to point at - but if a future feature genuinely calls for one, here's what you'd be signing up for. Measure again before merging: these numbers drift with compiler version, and being unused today doesn't make them free tomorrow.
| Header | Added cost (g++) | Added cost (clang++) |
|---|---|---|
| <regex> | +270ms (~3x) | +350ms (~3.5x) |
| <thread> | +200ms (~2.3x) | +240ms (~2.8x) |
Being on this page isn't a blanket ban on the standard library - it's a reminder to check before assuming. Two examples that already got measured and cleared:
A few places in TTS exist specifically because of a header that didn't clear this bar - useful reading before writing a new one from scratch: