C++26: #embed | Sandor Dargo's Blog<br>Sandor Dargo's Blog<br>On C++, software development and books
HOME TAGS ARCHIVES BOOKS SPEAKING DAILY C++ WORKSHOPS HI... SUBSCRIBE
Blog 2026 08 05 C++26: #embed Post<br>Cancel
C++26: #embed<br>Sandor Dargo Aug 5 2026-08-05T00:00:00+02:00<br>7 min
If you’ve ever needed to ship a binary file — a certificate, a small image, a default configuration — inside a C++ program, you know the ritual. You find or write a tool that converts the file into a C array, you wire it into your build system, and you pray that nobody forgets to re-run the conversion after updating the original file.<br>C++26 ends this with #embed (P1967R14 by JeanHeyd Meneide). Think of it as #include for binary data — a preprocessor directive that turns a file into a comma-separated sequence of integer constant expressions, directly at compile time, with no external tools.<br>Before #embed, every project rolled its own approach — xxd -i, objcopy, linker tricks, Python scripts, CMake file(READ) — each fragile, platform-specific, and one forgotten regeneration away from being stale.<br>#embed is also a perfect example of how long the standardization process can take. The first revision of P1967 was submitted in 2020, and it took fourteen revisions over five years before the committee voted it in. Along the way the syntax changed substantially as the design searched for consensus. And P1967 itself was a restart — earlier proposals like P1040 (std::embed) pushed for a non-preprocessor approach, embedding resources through a constexpr function rather than a directive. That path didn’t find enough support, and JeanHeyd eventually pivoted to the preprocessor-based design that made it through.<br>The syntax<br>#embed is a preprocessor directive. At its simplest:<br>const unsigned char icon[] = {<br>#embed "icon.png"<br>};
The directive reads icon.png and expands to a comma-separated list of integer constant expressions, one per byte. Each value is in the range [0, 255] (assuming CHAR_BIT == 8, which it is on every platform you care about). The result is exactly what xxd -i would have produced — but without the extra tool, the build step, or the generated file.<br>The resource identifier follows the same rules as #include: double quotes search implementation-defined paths (typically starting with the source file’s directory), and angle brackets search the system include paths:<br>#embed // system resource path<br>#embed "local_asset.bin" // local path first
Embed parameters<br>What makes #embed more than a built-in xxd are its four standard parameters. They are specified in parentheses after the resource identifier, using a syntax borrowed from attributes.<br>limit<br>Restricts how many elements are produced:<br>const unsigned char header[] = {<br>#embed "firmware.bin" limit(64)<br>};
This embeds only the first 64 bytes. Useful for pulling in just a file header, a magic number, or a fixed-size prefix without embedding the entire resource.<br>prefix and suffix<br>Prepend or append token sequences — but only when the resource is non-empty:<br>const unsigned char data[] = {<br>#embed "payload.bin" prefix(0xAA, 0xBB,) suffix(, 0xCC, 0xDD)<br>};
If payload.bin contains bytes {0x01, 0x02}, this expands to {0xAA, 0xBB, 0x01, 0x02, 0xCC, 0xDD}. If the file is empty, the prefix and suffix are silently omitted — you get an empty initializer, not a stray comma.<br>Note the trailing comma in prefix(0xAA, 0xBB,) and the leading comma in suffix(, 0xCC, 0xDD). These aren’t typos — they’re necessary because #embed expands to a token sequence that sits between the prefix and suffix. Without the trailing comma in the prefix, the last prefix token and the first embedded byte would be concatenated incorrectly.<br>if_empty<br>Provides fallback content when the resource exists but has zero bytes:<br>const unsigned char config[] = {<br>#embed "user_overrides.cfg" if_empty('{', '}')<br>};
If user_overrides.cfg is empty, you get {'{', '}'} — a minimal valid JSON object as raw bytes. If the file has content, if_empty is ignored. Note that when if_empty applies, prefix and suffix are also suppressed — you get exactly the if_empty tokens and nothing else.<br>__has_embed<br>You might not have seen this pattern before, but #include actually has a companion preprocessor test too — __has_include, available since C++17. Most of us never needed it because we control our own includes. #embed gets the same treatment with __has_embed, and here it’s more likely to be useful: the resource you want to embed might genuinely not exist in all build environments.<br>__has_embed lets you check whether a resource exists and whether it has content — before trying to embed it:<br>#if __has_embed("branding.png")<br>const unsigned char branding[] = {<br>#embed "branding.png"<br>};<br>#else<br>// fall back to a compiled-in default<br>const unsigned char branding[] = { /* ... */ };<br>#endif
__has_embed returns one of three values:<br>MacroValueMeaning__STDC_EMBED_NOT_FOUND__0Resource not found__STDC_EMBED_FOUND__1Found, non-empty__STDC_EMBED_EMPTY__2Found, but empty<br>Since...