惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
博客园 - 【当耐特】
NISL@THU
NISL@THU
IT之家
IT之家
博客园 - 叶小钗
M
MIT News - Artificial intelligence
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
量子位
The Register - Security
The Register - Security
爱范儿
爱范儿
酷 壳 – CoolShell
酷 壳 – CoolShell
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Security Affairs
W
WeLiveSecurity
S
Security @ Cisco Blogs
Apple Machine Learning Research
Apple Machine Learning Research
V2EX - 技术
V2EX - 技术
The Last Watchdog
The Last Watchdog
Blog — PlanetScale
Blog — PlanetScale
美团技术团队
J
Java Code Geeks
P
Proofpoint News Feed
大猫的无限游戏
大猫的无限游戏
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Last Week in AI
Last Week in AI
腾讯CDC
Cisco Talos Blog
Cisco Talos Blog
C
Check Point Blog
人人都是产品经理
人人都是产品经理
Forbes - Security
Forbes - Security
SecWiki News
SecWiki News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
B
Blog
S
Secure Thoughts
T
Threat Research - Cisco Blogs
P
Privacy & Cybersecurity Law Blog
N
News | PayPal Newsroom
The GitHub Blog
The GitHub Blog
Recorded Future
Recorded Future
Google DeepMind News
Google DeepMind News
博客园 - 聂微东
V
Visual Studio Blog
L
LINUX DO - 最新话题
Recent Commits to openclaw:main
Recent Commits to openclaw:main
O
OpenAI News
Webroot Blog
Webroot Blog
Hacker News: Ask HN
Hacker News: Ask HN

Hacker News

Introducing Claude Opus 4.7 Qwen Studio The Future of Everything is Lies, I Guess: Where Do We Go From Here? GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis Moving a large-scale metrics pipeline from StatsD to OpenTelemetry / Prometheus GitHub - Nightmare-Eclipse/RedSun: The Red Sun vulnerability repository GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. GitHub - macOS26/Agent: Any AI, replaces Claude Code, Cursor, OpenClaw. Over 18 LLM providers (Claude, OpenAI, Gemini, Ollama, Zai, HF, Qwen) wired into a native Mac app that writes code, builds Xcode projects, bumps versions, manages git, automates Safari, use AppleScript, JS or Accessibility, extend Agent! w/ MCP Servers, run tasks from your iPhone via Messages. YouTube now lets you turn off Shorts I Made a Terminal Pager Burgers | マクドナルド公式 Commands — HackerNews CLI documentation ChatGPT for Excel PiCore - Raspberry Pi Port of Tiny Core Linux Live Nation illegally monopolized ticketing market, jury finds Google Broke Its Promise to Me. Now ICE Has My Data. Founding Engineer at Adaptional | Y Combinator CRISPR takes important step toward silencing Down syndrome’s extra chromosome GitHub - saffron-health/libretto: The AI toolkit for building reliable browser automations US v. Heppner (S.D.N.Y. 2026) no attorney-client privilege for AI chats [pdf] Unexpected €54k billing spike in 13 hours: Firebase browser key without API restrictions used for Gemini requests Retrofitting JIT Compilers into C Interpreters IPv6 – Google The Accursèd Alphabetical Clock Cybersecurity Looks Like Proof of Work Now Fragments: April 14 Cal.com Goes Closed Source: Why AI Security Is Forcing Our Decision | Cal.com - Scheduling Software for Online Bookings Laravel raised money and now injects ads directly into your agent When moving fast, talking is the first thing to break Too much Discussion of the XOR swap trick – Heather Cafe Introduction to Spherical Harmonics for Graphics Programmers The Grand Line Building a Z-Machine in the worst possible language High-Level Rust: Getting 80% of the Benefits with 20% of the Pain GitHub - duguyue100/midnight-captain: Inspired by Midnight Commander, tailored to my taste. How to build a `git diff` driver · Jamie Tanna | Software Engineer Center for Responsible, Decentralized Intelligence at Berkeley The Local Universe’s Expansion Rate Is Clearer Than Ever, but Still Doesn’t Add Up - A new synthesis of astronomical measurements confirms a persistent mismatch that could point to physics beyond current models The air throughout our homes is infused with microplastics. But there are things you can do to breathe less of them The disturbing white paper Red Hat is trying to erase from the internet – OSnews The Future of Everything is Lies, I Guess: Annoyances ‘Abhorrent’: the inside story of the Polymarket gamblers betting millions on war Productive procrastination — Max van IJsselmuiden maps, territory and LMs 447 Terabytes per Square Centimetre at Zero Retention Energy: Non-Volatile Memory at the Atomic Scale on Fluorographane Show HN: Pardonned.com – A searchable database of US Pardons 20 Years on AWS and Never Not My Job The Seasons are Wrong Artemis II crew splashes down near San Diego after historic moon mission We gave an AI a 3 year retail lease in SF and asked it to make a profit | Andon Labs How a dancer with ALS used brainwaves to perform live On filing the corners off my MacBooks Installing every* Firefox extension OpenClaw’s memory is unreliable, and you don’t know when it will break Steve Blank Nowhere Is Safe Chimpanzees in Uganda locked in vicious 'civil war', say researchers watgo - a WebAssembly Toolkit for Go linux/Documentation/process/coding-assistants.rst at master · torvalds/linux GitHub - callumlocke/json-formatter: Makes JSON easy to read. Founding Product Engineer at Bild AI | Y Combinator A compelling title that is cryptic enough to get you to take action on it GitHub - Keychron/Keychron-Keyboards-Hardware-Design: Industrial design files for Keychron keyboards and mice. 100+ models with CAD assets in STEP, DXF, DWG, and PDF. Source-available, with commercial use allowed for original compatible accessories within the license terms. [ANNOUNCE] WireGuardNT v0.11 and WireGuard for Windows v0.6 Released 1D-Chess Helium Is Hard to Replace Cooperative Vectors Introduction | Evolve Keeping a Postgres queue healthy — PlanetScale Our response to the Axios developer tool compromise Do Americans read print books, e-books or audiobooks more? The Zettelkasten Method in Obsidian: A Practical Setup Guide Artemis II Is Competency Porn and We Are Starving For It WeakC4 Flight Viz — Cockpit View A Mexican surveillance giant you’ve never heard of is now watching the U.S. border Surelock: Deadlock-Free Mutexes for Rust RISC-V 101 – what is it and what does it mean for Canonical? | Ubuntu The Problem That Built an Industry How Much Linear Memory Access Is Enough? | Solidean Investigating Split Locks on x86-64 Simplest hash functions Sybilproof reputation mechanisms (2005) [pdf] What is a property? How Complex is my Code? Static code analysis in Kotlin — tools overview Toffoli gates are all you need PGLite evangelism dcmake: a new CMake debugger UI Clojure on Fennel part one: Persistent Data Structures Fragments: April 2 Python Release Python install manager 26.1 The Life and Death of the Book Review - Liberties Introducing Database Traffic Control — PlanetScale Bitcoin miners are losing $19,000 on every BTC produced as difficulty drops 7.8% God sleeps in the minerals Building slogbox Apple Silicon and Virtual Machines: Beating the 2 VM Limit Who was “Not Even Wrong” first? Pokemon Evolution Vs Darwinian Evolution The APL Programming Language Source Code
cost of enum-to-string: C++26 reflection vs the old ways
sagacity · 2026-05-13 · via Hacker News

Two months ago I published “the hidden compile-time cost of C++26 reflection”, where I measured what including <meta> and doing some basic reflection actually costs per translation unit. If you haven’t read it, start there – this post builds directly on top of it.

That article used a prerelease GCC 16 snapshot. Since then, GCC 16 has been officially released1 and is now widely available, which seemed like a good excuse to revisit the topic with a more realistic example: enum-to-string conversion.

Enum-to-string is the “hello world” of reflection – but it’s also genuinely useful in real projects, for things like logging, serialization, debugging, and so on. If you adopt reflection in a real codebase, it might be the first thing you’ll write.

So: how much does reflection-based enum-to-string actually cost, in compile time, compared to the alternatives?

the three approaches

I benchmarked three implementations of the same operation: given an enum value, return a std::string_view with its enumerator name.

1. reflection (c++26)

No macros, no boilerplate, works for any enum:

#include <meta>
#include <string_view>
#include <type_traits>

template <typename T>
    requires std::is_enum_v<T>
constexpr std::string_view to_enum_string(T val)
{
    template for (constexpr auto e :
        std::define_static_array(std::meta::enumerators_of(^^T)))
    {
        if (val == [:e:])
            return std::meta::identifier_of(e);
    }

    return "<unknown>";
}

The code was taken from “What the heck is Reflection?” by Murat Hepeyiler. I think it’s a pretty idiomatic example.

2. enchantum (c++17)

A C++17 header-only library by ZXShady that achieves enum reflection through __PRETTY_FUNCTION__ parsing tricks. No macros at the call site, no reflection flag needed:

#include <enchantum/enchantum.hpp>

enum class E { V0, V1, V2, V3 };

std::string_view s = enchantum::to_string(E::V0);

3. x-macro (preprocessor)

The C-style solution. You list the enumerators once, and a single macro expands into both the enum class definition and a to_string function that uses a switch:

#include "xmacro_enum.hpp"

#define E_LIST(X) \
    X(V0) X(V1) X(V2) X(V3)

DEFINE_ENUM(E, E_LIST)

// Generates:
//   enum class E { V0, V1, V2, V3 };
//   constexpr std::string_view to_string(E e) { ... }

The implementation of DEFINE_ENUM is a few lines of preprocessor glue:

#include <string_view>

#define XMACRO_VALUE_(name) name,

#define XMACRO_CASE_(name) \
    case EnumType_::name: return #name;

#define DEFINE_ENUM(x_enum_name, x_list)                \
    enum class x_enum_name { x_list(XMACRO_VALUE_) };   \
    constexpr std::string_view to_string(x_enum_name e) \
    {                                                   \
        using EnumType_ = x_enum_name;                  \
        switch (e) { x_list(XMACRO_CASE_) }             \
        return "<unknown>";                             \
    }

The only header it pulls in is <string_view>. We’ll also test a variant of this approach that returns const char* instead, with zero standard library includes.

the benchmark

For each approach, I created several translation units that:

  1. Define a single enum class E with N enumerators (V0, V1, …, V(N-1));
  2. Include the enum-to-string header;
  3. Call the conversion function once with a runtime value to force instantiation.

I varied N across 4, 16, 64, 256, and 1024 to see how cost scales with enum size.

For example, the reflection variant for N=4 looks like this:

#include "enum_to_string.hpp"

enum class E { V0, V1, V2, V3 };

volatile int runtime_idx = 0;

int main()
{
    const E val = static_cast<E>(runtime_idx);
    const auto sv = to_enum_string(val);
    return static_cast<int>(sv.size());
}

The enchantum and X-macro variants are structurally identical – only the header and the function call change. I also separately measured the cost of just including each header, to isolate the header tax from the reflection work itself.

⚠️ Note on enchantum’s default range: enchantum scans enum values across a configurable range (default [-256, 256]). For N=1024, I had to bump ENCHANTUM_MAX_RANGE to 1024, which also slows down every other enum in the same TU. Keep that in mind when reading the N=1024 row.

All benchmark files are available on GitHub.

benchmarking setup

Same as the previous articlehyperfine inside a Fedora 44 Docker container on a 13th Gen i9-13900K. Two things are different this time:

  • Compiler: gcc 16.1.1 20260501 (Red Hat 16.1.1-1, release build) – the officially released GCC 16, not a prerelease snapshot.

  • Noise control: the container ran with --cpuset-cpus=0-7, the host was set to the performance CPU governor, and the compiler process was pinned to a P-core with taskset -c 0. hyperfine was run with --warmup 5 --min-runs 20.

Usual disclaimer: measurements aren’t strictly rigorous, my hardware is beefy (YMMV), and single-TU numbers undersell project-wide cost.

Also, these measurements are specific to GCC 16’s current reflection and module implementation; other compilers may exhibit very different behavior.

benchmark results

total per-TU compile time

N X-macro (const char*) X-macro (string_view) enchantum Reflection
Baseline (int main()) 25.8 ms 25.7 ms 25.8 ms 25.7 ms
Header include only 25.7 ms 136.0 ms 147.1 ms 180.8 ms
4 26.6 ms 137.6 ms 170.6 ms 186.7 ms
16 26.9 ms 138.1 ms 170.9 ms 187.7 ms
64 28.0 ms 141.2 ms 172.8 ms 191.1 ms
256 32.5 ms 153.0 ms 184.1 ms 215.0 ms
1024 54.7 ms 204.5 ms 272.0 ms2 255.0 ms

algorithm-only cost (TU time minus include-only time)

This approximates the additional reflection work beyond header inclusion:

N X-macro (const char*) X-macro (string_view) enchantum Reflection
4 0.9 ms 1.6 ms 23.5 ms 5.9 ms
16 1.2 ms 2.1 ms 23.8 ms 6.9 ms
64 2.3 ms 5.2 ms 25.7 ms 10.3 ms
256 6.8 ms 17.0 ms 37.0 ms 34.2 ms
1024 29.0 ms 68.5 ms 124.9 ms 74.2 ms

per-enumerator scaling

Approach ms / enumerator
X-macro (const char*) ~0.027
X-macro (string_view) ~0.06
Reflection ~0.07
enchantum O(scan range), not O(N)

enchantum does not scale with the actual enum size – it scales with the configured scan range, since it has to probe every possible value in that range. That’s why an N=4 enum costs almost as much as N=64.

reflection with PCH and modules

Since the reflection variant pays a ~155 ms header tax for <meta>, the obvious question is: does precompiling the header or switching to C++20 modules eliminate it?

I re-ran the reflection benchmark with two extra configurations:

  • PCH: precompiled <meta>, <string_view>, <type_traits> once, then compiled the TUs with -include pch.hpp.

  • Modules: pre-built the std and std.compat modules and the <bits/stdc++.h> header unit once via GCC 16’s new --compile-std-module flag (this cost is not included in the measurement), then compiled the TUs with -fmodules so that #include <meta> is transparently translated into import <bits/stdc++.h>.

N Reflection (plain #include) Reflection + PCH Reflection + modules
Header include only 180.8 ms 73.8 ms 397.4 ms
4 186.7 ms 80.6 ms 403.4 ms
16 187.7 ms 81.0 ms 403.1 ms
64 191.1 ms 84.4 ms 409.4 ms
256 215.0 ms 97.5 ms 423.2 ms
1024 255.0 ms 147.9 ms 482.5 ms

PCH is the clear winner – about ~2.3x speedup at every enum size, dropping N=4 from 187 ms to 81 ms. With PCH in place, reflection beats both enchantum and the string_view X-macro variant outright.

Modules are the opposite: about ~2.2x slowdown. I verified that the std module artifacts were genuinely cached (mtime unchanged across runs) and that GCC was loading them (confirmed via -flang-info-module-cmi and -ftime-report, which attributed ~190 ms to module import plus another ~190 ms to template instantiation work the module triggered).3

Explicit import std; performs essentially the same as the transparent translation, because GCC’s std module is currently implemented as a thin wrapper around the <bits/stdc++.h> header unit – both routes end up loading the same ~34 MB artifact.

insights

  1. The header is the cost. Not the reflection. The reflection algorithm is fast – asymptotically ~0.07 ms per enumerator, essentially the same as the hand-rolled switch in the X-macro version (~0.06 ms). What makes reflection look expensive is <meta>: just including it costs ~155 ms per TU over the baseline.

  2. The X-macro with const char* is the fastest tested approach. With zero standard library headers, an N=4 enum compiles in 26.6 ms – within the noise of the baseline. Even N=1024 (54.7 ms) is faster than just #include <meta> with no reflection work at all. Most of what we call “slow C++ compilation” is really slow standard library compilation.

  3. enchantum has the smallest include cost of the non-trivial approaches4 (~147 ms vs reflection’s ~181 ms), but the heaviest per-call work (~24 ms even for tiny enums, because it always scans the full configured range, regardless of how many enumerators you actually have). That’s why it wins on small enums and loses on large ones.

  4. Reflection has the best ergonomics but the highest header tax. It works for any enum – sparse, scoped, unscoped – with no special setup at the declaration site. But every TU that touches <meta> pays ~155 ms before any reflection happens.

  5. PCH closes the gap, modules widen it. Precompiling <meta> cuts reflection compile time by ~2.3x and makes it the fastest of the three approaches. C++20 modules in GCC 16, surprisingly, go the other way – ~2.2x slower than the plain include path.

what this means in a real codebase

The single-TU numbers look small. They are not, at scale.

A large C++ codebase can easily have a few hundred translation units that pull in the enum-to-string header, perhaps transitively. Picking 500 TUs as a round number, and an N=16 enum as a typical size:

Approach Per-TU cost Project-wide cost (500 TUs)
X-macro (const char*) 26.9 ms ~13 seconds
X-macro (string_view) 138.1 ms ~69 seconds
enchantum 170.9 ms ~85 seconds
Reflection 187.7 ms ~94 seconds

A few hundred milliseconds per TU turns into over a minute of compile time at the project level. That’s the difference between a sub-15-second clean build and a minute and a half. Incremental builds won’t always save you, because every TU that includes an affected header pays the full price.

This is multiplied by every header in your project that has similar overhead. Real codebases don’t have one heavy header – they have dozens. The few hundred milliseconds you see in a microbenchmark become minutes once you multiply.

On the other hand, the numbers shown here do not take parallelism into account. E.g. the ~94 CPU-seconds would be ~6s on a 16-core machine, assuming perfect parallelism.

what to do about it

If you’re adopting reflection-based enum-to-string in a large codebase:

  1. Use PCH for <meta> – not modules. As shown above, a PCH cuts the header cost by ~2.3x and makes reflection the fastest of the three approaches. C++20 modules in GCC 16 do the opposite right now (they make things ~2.2x slower), so this is one of those rare cases where the older mechanism is the right answer.

  2. Don’t include the enum-to-string header from other headers. Push it as far down the include graph as possible – ideally, only into the .cpp files that actually need it. Every transitive include multiplies the cost.

  3. For compile-time-sensitive code, X-macros are still the right answer. They look ugly, they don’t compose, they force you to list enumerators in a macro. But 27 ms per TU is hard to beat. For a project like my fork of SFML, where the entire codebase rebuilds in ~4 seconds, anything with <meta> in it is a non-starter.

  4. For library authors, think twice before exposing reflection through your public headers. A library header that drags <meta> into every consumer’s TU is something I personally would not want to depend on. enchantum or an X-macro is a much friendlier choice until <meta> gets lighter or modules become ubiquitous.

conclusion

This benchmark confirms what the original article argued, now with concrete numbers from an operation that people will actually write:

The cost of C++26 reflection is not the reflection. It’s <meta>.

The reflection algorithm itself seems to be quite fast. The reason a reflection-based to_string ends up ~7x slower to compile than the bare-metal const char* X-macro variant is almost entirely <meta> (and its transitively-included headers).

I’m still excited about reflection. It will replace a lot of ugly macro boilerplate and unlock libraries that weren’t really possible before. But until <meta> gets lighter (or until modules get much faster), every project that adopts it should know what the bill looks like.

shameless self-promotion


  1. As of writing, GCC 16 has landed in Arch Linux’s official repositories, in Fedora 44, and in most rolling distributions. The Fedora image used in this benchmark ships gcc 16.1.1 20260501, built with --enable-checking=release --with-build-config=bootstrap-lto.↩︎

  2. With -DENCHANTUM_MAX_RANGE=1024. The default would not have compiled.↩︎

  3. This is presumably a transient issue. Module loading hasn’t been optimized to PCH levels yet. I expect this gap to eventually close, but as of today, modules are not a viable optimization for <meta>-heavy TUs.↩︎

  4. I suspect it could be optimized even further by avoiding the Standard Library in lieu of bespoke components and compiler builtins.↩︎