# C++26 Reflection

18 september 2026 | https://www.ribomation.se/blog/2026/c++26-reflection/

Hur många gånger har du skrivit samma `to_string()` en gång till? Samma fem rader, samma fältnamn utskrivna för hand, samma risk att glömma det fält någon la till förra veckan. Kompilatorn vet redan allt som behövs — den har hela typen framför sig — men fram till nu har den inte velat berätta. I C++26 gör den det.

Reflection i C++ är dock inte det du känner igen från Java eller C#. Där är det en service som utförs medan programmet exekverar, med metadata i binären och uppslagning via strängar. I C++26 är en reflektion ett `constexpr`\-värde som skapas och konsumeras _under kompileringen_, och som inte finns kvar efteråt. Java frågar "hur ser det här objektet ut just nu?"; C++ frågar "hur ser den här typen ut, så att jag kan skriva kod åt den?". Det är inte en runtime-tjänst — det är en kodgenerator som bor i kompilatorn.

I den här artikeln går jag igenom de fyra nya syntaxelementen — `^^` som reflekterar, `[: :]` som splicar tillbaka, `template for` som rullar ut en loop vid kompilering, och `[[= ]]` som hänger metadata på en deklaration — samt `std::meta::info`, värdet som allt kretsar kring. Sedan bygger vi en generisk `stringify()` på 25 rader som renderar vilken struct som helst utan en enda rad skriven per typ, och använder samma splice för att _skriva_ till fälten också. Därefter tar vi isär vad `template for` faktiskt kompilerar till, rad för rad, och konstaterar att det som återstår vid körning är precis den kod du hade skrivit för hand — en poäng som demokoden kontrollerar åt oss genom att jämföra tre gradvis expanderade varianter. Vi tittar också på vad GCC 16.1 vill ha för flaggor och vilket felmeddelande du får när du glömmer dem. Allt är byggt och kört på riktigt, och ligger på GitHub.

Nästa vecka tar vi annotationerna på allvar och bygger vidare på det här: en liten ORM som härleder `CREATE TABLE`, `INSERT`, `UPDATE`, `DELETE`, `SELECT` ur vanliga structar, utan makron och utan kodgenerator.

* * *

Vad är reflection?
------------------

Reflection är ett programs förmåga att undersöka sin egen struktur. Typer, medlemmar, funktioner, namnrymder — sådant som normalt bara finns som _syntax_ i källkoden blir istället tillgängligt som **data**, som programmet kan ställa frågor om.

Det brukar delas i två halvor:

*   **Introspection** — att fråga "hur ser den här typen ut?" och få ett svar man kan agera på. Vilka fält finns? Vad heter de? Vilka typer har de?
*   **Generation** — att göra något med svaret. Bygga en serializer, en `enum`\-till-sträng-funktion, en databindning, en kommandoradsparser.

Det är den andra halvan som gör det intressant. Introspection ensamt är en kuriositet; det är när svaret får bli till ny kod som man slipper skriva samma sak en gång per typ.

Och det är just "en gång per typ" som är problemet reflection löser. Alla som har skrivit en `to_string()`, en JSON-serializer eller en `operator<<` för ett par dussin structar vet hur det ser ut: samma fem rader, om och om igen, med fältnamnen utbytta. Kompilatorn _vet_ redan allt som behövs — den har hela typen framför sig — men fram till nu har den inte velat berätta.

Helt nytt är det inte för C++. Vi har `<type_traits>`, som kan svara på en del frågor (`is_pointer_v`, `is_aggregate_v`, ...), och vi har makron, som kan generera kod. Men `<type_traits>` kan bara svara på de frågor någon har skrivit ett trait för, och makron vet ingenting om typer alls — de klipper och klistrar text. Kombinationen har approximerat reflection i tjugo år, med varierande grad av elegans.

C++26 gör det till en riktig språkfunktion.

* * *

Reflection i andra språk
------------------------

Innan vi tittar på C++-varianten är det värt att veta vad ordet betyder för alla andra, eftersom C++ menar något ganska annorlunda med det.

**Java och C#** har det man brukar tänka på när man hör "reflection". Ett `Class<?>`\- eller `Type`\-objekt finns tillgängligt vid körning, med metoder som `getMethods()` och `getFields()`. Man kan inspektera ett levande objekt, plocka ut ett fält man inte visste namnet på förrän i runtime, anropa en metod via en sträng, och i vissa fall ändra saker som källkoden inte ens visste fanns. Det är kraftfullt, det är grunden för hela ramverk som Spring och Hibernate — och det kostar. Metadata följer med in i den körbara artefakten, uppslagningen sker vid körning, och JIT-kompilatorn får svårare att optimera.

**Python** har inte reflection som en påbyggnad. Det _är_ språket. `dir()`, `getattr()`, `__dict__` — allt är slagbart, alltid, och ett objekt kan få nya attribut medan programmet kör. Frågan "vad kostar det?" är irrelevant, eftersom allting redan kostar.

**Rust** har ingen generell reflection alls. Det man använder istället är derive- och procedurmakron, som kör vid kompilering och genererar kod — men de arbetar på _syntax_, på en token-ström, inte på en typmodell man kan ställa frågor till. Ett proc-makro som ser `struct Account { iban: IBAN }` vet att det står `IBAN` där; det vet inte att `IBAN` står för _International Bank Account Number_ och hur ett dylikt objekt ska hanteras.

* * *

Det speciella med C++-varianten
-------------------------------

C++26 passar inte in i något av de facken. Reflection i C++ är **enbart en compile-time-företeelse**.

En reflektion är ett `constexpr`\-värde av typen `std::meta::info`. Det skapas vid kompilering, konsumeras vid kompilering, och finns inte kvar efteråt. Det finns inga typobjekt att slå upp vid körning, ingen metadatatabell i binären, ingen uppslagning via sträng. Inget hamnar i den körbara filen utom exakt det du explicit har genererat.

Skillnaden är lättast att se i vilken fråga språken ställer:

> **Java och C# frågar:** _"hur ser det här objektet ut just nu?"_
> 
> **C++ frågar:** _"hur ser den här typen ut, så att jag kan skriva kod åt den?"_

Det är samma ord för två rätt olika saker. Java-varianten är en service som programmet anropar. C++-varianten är en **kodgenerator som körs i kompilatorn**.

Det ligger helt i linje med hur C++ brukar resonera. Zero-overhead-principen säger att man inte ska betala för det man inte använder, och att det man använder inte ska gå att skriva bättre för hand. Reflection i C++26 uppfyller båda: använder du det inte kostar det noll, och använder du det får du exakt den kod du hade skrivit själv — för det _är_ den kod du hade skrivit själv, bara utstansad av kompilatorn istället för av dig.

> Priset för det är att allt som kräver dynamik är uteslutet. Du kan inte ladda in en plugin och fråga vad den innehåller, inte slå upp ett fält utifrån en sträng som kommer från en konfigurationsfil, inte bygga något i stil med Spring Framework/Boot. Det är inte en brist som ska fixas senare — det är designen. Vill du ha den sortens dynamik får du fortsatt bygga den själv, ovanpå den statiska modellen.

* * *

Fyra nya syntaxelement
----------------------

Reflection kommer med fyra nya bitar syntax. De är ovanliga att se på, men det är bara fyra — och de hänger ihop parvis.

Syntax

Namn

Gör

`^^T`

reflektionsoperatorn

entitet → `std::meta::info`

`[: r :]`

splice-operatorn

`std::meta::info` → entitet

`template for`

expansion statement

loop som rullas ut vid kompilering

`[[= expr ]]`

annotation

fäster metadata på en deklaration

### Reflektionsoperatorn `^^`

`^^` — av uppenbara skäl kallad _cat ears_ — är vägen in. Den appliceras på i princip vilken entitet som helst: en typ, en variabel, en funktion, ett enum-värde, en namnrymd, en template.

```cpp
constexpr auto r1 = ^^int;              // en typ
constexpr auto r2 = ^^std::string;      // också en typ
constexpr auto r3 = ^^twice;            // en funktion
constexpr auto r4 = ^^schema;           // en namnrymd
constexpr auto r5 = ^^schema::Account;  // en typ inuti namnrymden
```

Resultatet är alltid detsamma: ett objekt av typen `std::meta::info`. Det är avsiktligt. Alla entiteter, oavsett vad de är, blir värden av en och samma typ — vilket är precis vad som krävs för att kunna lägga dem i en `std::vector` och loopa över dem.

### `std::meta::info` — valutan

`std::meta::info` är ogenomskinlig i sig själv. Allt intressant sker i de funktioner som tar emot den. De kommer i två familjer.

**Extraktorer**, som plockar isär en entitet och ger nya `info`\-värden tillbaka:

```cpp
members_of(r, ctx)                // alla medlemmar i en klass
nonstatic_data_members_of(r, ctx) // bara de icke-statiska datafälten
bases_of(r, ctx)                  // basklasserna
enumerators_of(r)                 // enum-medlemmarna och deras värden
type_of(r)                        // typen hos en medlem eller variabel
identifier_of(r)                  // namnet, som en sträng
annotations_of(r)                 // annotationerna (mer om dem i nästa artikel)
```

**Predikat**, som svarar ja eller nej:

```cpp
is_type(r)          is_variable(r)       is_function(r)
is_constructor(r)   is_public(r)         is_const(r)
```

…och en bra bit fler i båda familjerna. Poängen är att `info` både går in och kommer ut: `type_of(m)` på en medlem ger dig en `info` som beskriver medlemmens typ, som du i sin tur kan ställa nya frågor om. Det är en typmodell man kan navigera i, inte en platt lista.

> `access_context`\-argumentet (`ctx` ovan) är C++ som är sig likt: åtkomstregler gäller även här. En reflektion tagen utifrån klassen ser inte dess privata medlemmar. Man skapar kontexten med `std::meta::access_context::current()`, och får då exakt den åtkomst den omgivande koden har.

### Splice-operatorn `[: :]`

Splicen är motsatsen till `^^`. Där `^^T` gör om en entitet i programmet till ett värde, gör en splice om värdet tillbaka till en entitet som kompilatorn agerar på.

Det är den halvan som gör det hela användbart. Att veta att `Point3D` har ett fält som heter `x` är trevligt; att kunna skriva `p.x` utan att ha skrivit bokstäverna `x` är poängen.

Splicen har fyra former, beroende på vad `info`\-värdet betecknar:

```cpp
[: r :]                 // en variabel, en funktion, ett enum-värde, ...
typename [: r :]        // en typ
template [: r :]        // ett template-namn
obj.[: r :]             // ett icke-statiskt fält i objektet obj
```

### Expansion statement: `template for`

`template for` är en loop som körs i kompilatorn. Den itererar över en sekvens som är känd vid kompilering — en `std::define_static_array()` av `info`\-värden, en `std::tuple<T,...>`, en `constexpr` range — och istället för att generera en loop i den färdiga koden **rullar den ut kroppen**, en kopia per element.

Det låter som en optimering, men det är en språkfunktion, och skillnaden är viktig: i varje utrullad kopia är loopvariabeln en _konstant_, och den **kan ha olika typ i olika varv**. Det är det som ingen vanlig `for` klarar, och skälet till att det behövdes en ny sats och inte bara ett bibliotekstrick.

### Annotationer `[[= ]]`

Den fjärde biten nämner jag bara i förbigående här, för den är hela ämnet för nästa artikel. Med `[[= uttryck ]]` hänger man ett godtyckligt `constexpr`\-värde på en deklaration, och `annotations_of` läser tillbaka det. Metadata i koden, läsbar av koden.

* * *

Exempel: `stringify(obj)`
-------------------------

Nog med syntax. Här är en generisk `stringify()` som renderar vilken aggregat-struct som helst — utan en rad skriven per typ.

Först två helt vanliga structar, utan något speciellt alls:

```cpp
struct Point3D {
    double x = 0.0;
    double y = 0.0;
    double z = 0.0;
};

struct Employee {
    std::string name;
    int         age;
    double      salary;
};
```

Och så funktionen:

```cpp
template<typename TargetType>
auto stringify(TargetType const& obj) -> std::string {
    static constexpr auto ctx     = access_context::current();
    static constexpr auto members = define_static_array( nonstatic_data_members_of(^^TargetType, ctx) );
    std::string result{};
    string_view typeName = identifier_of(^^TargetType);

    result += typeName + " {"s;
    template for (bool first=true; constexpr auto m : members) {
        string_view name  = identifier_of(m);
        string_view type  = display_string_of( type_of(m) );
        auto value        = obj.[: m :];

        if (first) first=false; else result += ", ";
        if (type.starts_with("std::__cxx11::basic_string"sv)) {
            type = "std::string"sv;
            result += format("{}:{} = \"{}\"", name, type, value);
        } else {
            result += format("{}:{} = {}", name, type, value);
        }
    }
    result += "}";

    return result;
}
```

Rad för rad, i den ordning det händer:

*   `^^TargetType` reflekterar mallparametern. Vi vet inte vilken typ det är — men kompilatorn gör det, vid varje instansiering.
*   `nonstatic_data_members_of(..., ctx)` ger tillbaka en `std::vector<std::meta::info>`, ett element per datafält.
*   `define_static_array(...)` flyttar upp den vektorn i statisk lagring. Det behövs eftersom vektorn är en temporär under kompileringen, och `template for` behöver en sekvens som lever kvar. Resultatet är en `std::span` in i en array som kompilatorn har materialiserat.
*   `identifier_of(^^TargetType)` ger typens namn som sträng. Där försvann behovet av att hårdkoda `"Point3D"` någonstans.
*   `template for (...)` rullar ut kroppen, en gång per fält.
*   `identifier_of(m)` ger fältets namn, `display_string_of(type_of(m))` dess typ.
*   `obj.[: m :]` splicar in medlemmen som en faktisk fältåtkomst. **Det här är hela grejen.** I det första varvet _är_ det uttrycket `obj.x`; i det andra `obj.y`.

Anropet ser ut som vilket funktionsanrop som helst:

```cpp
auto p = Point3D{1, 2.5, -3};
std::println("p: {}", stringify(p));

auto e = Employee{"Nisse", 42, 12345.0};
std::println("e: {}", stringify(e));
```

```text
p: Point3D {x:double = 1, y:double = 2.5, z:double = -3}
e: Employee {name:std::string = "Nisse", age:int = 42, salary:double = 12345}
```

Notera att `Employee` innehåller en `std::string` och `Point3D` tre `double`, och att `stringify` inte vet något om vare sig det ena eller det andra. Det är samma 25 rader kod för båda.

> Den lilla `starts_with`\-fulheten mitt i funktionen förtjänar en förklaring. `display_string_of(type_of(m))` på ett `std::string`\-fält ger inte `"std::string"` — det ger `"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >"`, eftersom det är vad typen faktiskt heter i libstdc++. Det är helt korrekt och helt oanvändbart i en utskrift, så jag städar bort det. En riktig implementation skulle jämföra mot `^^std::string` istället för att matcha på sträng — men den här varianten visar bättre vad kompilatorn egentligen lämnar ifrån sig.

### Samma sak, fast åt andra hållet

En splice är ett uttryck som betecknar medlemmen, inte en kopia av dess värde. Alltså går den att skriva till:

```cpp
auto p = Point3D{5, 7.5, -3.25};

static constexpr auto ctx     = access_context::current();
static constexpr auto members = define_static_array( nonstatic_data_members_of(^^Point3D, ctx) );

template for (auto k = 2U; constexpr auto m: members) {
    p.[: m :] *= k++ * 10;
}
```

```text
[before] Point3D {x:double = 5, y:double = 7.5, z:double = -3.25}
[after]  Point3D {x:double = 100, y:double = 225, z:double = -130}
```

Lägg märke till `auto k = 2U` i loopens init-del. Den variabeln deklareras **en gång**, utanför kopiorna, och är en helt vanlig körtidsvariabel som alla utrullade block delar. Det är därför `k++` räknar upp genom expansionen: 2, 3, 4 — vilket ger faktorerna 20, 30 och 40.

* * *

Vad händer egentligen?
----------------------

Det här är den bit som är värd att verkligen stanna upp vid, för när den väl har lagt sig på plats är resten mest detaljer.

`template for` gör en enda sak: kompilatorn tar loopkroppen och **stansar ut en kopia per element** i sekvensen, och substituerar in elementet som en konstant i varje kopia.

```cpp
template for (constexpr auto m : members) { p.[: m :] *= 10; }
```

blir, för `Point3D`:

```cpp
{ p.x *= 10; }
{ p.y *= 10; }
{ p.z *= 10; }
```

Och `stringify`\-kroppen, för `Employee`, blir den expanderade koden ungefär på följande vis:

```cpp
bool first = true;
string_view typeName = "Employee"sv;
string result = typeName + " {"s;
{
    // ---- Employee::name  (std::string) ----
    string_view name  = "name";
    string_view type  = "std::string";
    std::string value = obj.name;

    if (first) first = false; else result += ", ";
    result += format("{}:{} = \"{}\"", name, type, value);
}
{
    // ---- Employee::age  (int) ----
    string_view name  = "age";
    string_view type  = "int";
    int         value = obj.age;

    if (first) first = false; else result += ", ";
    result += format("{}:{} = {}", name, type, value);
}
{
    // ---- Employee::salary  (double) ----
    string_view name  = "salary";
    string_view type  = "double";
    double      value = obj.salary;

    if (first) first = false; else result += ", ";
    result += format("{}:{} = {}", name, type, value);
}
```

Titta på `value`. I första blocket är den en `std::string`, i andra en `int`, i tredje en `double` — trots att källkoden bara innehåller ett enda `auto value`. Det är den egenskapen som gör `template for` till en ny sats istället för ett bibliotekstrick: varje kopia är sin egen kontext, med sina egna typer.

Titta sedan på `if (first)`. Den finns kvar, i alla tre blocken, som ett vanligt körtidstest. `first` deklarerades i loopens init-del, alltså _en_ gång utanför kopiorna. Kompilatorn rullar ut kroppen — den räknar inte ut vad koden gör. (Att optimeraren sedan ser att `first` är sant i första blocket och falskt i de andra, och slänger testerna, är en helt annan sak. Det är konstantfoldning, inte expansion.)

Och när expansionen är klar är resultatet **helt vanlig C++**. De tre blocken kompileras till vanliga maskininstruktioner, hamnar i textsegmentet, och körs som all annan kod. Det finns ingen `std::meta::info` kvar i binären. Det finns ingen metadatatabell, inget uppslagsregister, ingenting att slå upp. Bara de instruktioner du hade fått om du hade skrivit de tre blocken själv. Den kod som blir kvar ser väl ut ungefär som denna:

```cpp
string result{"Employee {"};
result += format("{}:{} = \"{}\"", "name", "std::string", obj.name);
result += ", ";
result += format("{}:{} = {}"    , "age", "int", obj.age);
result += ", ";
result += format("{}:{} = {}"    , "salary", "double", obj.salary);
result += "}";
```

> Att det verkligen är så behöver man inte ta mitt ord på. I demokoden ligger `stringify-expansion.cxx`, där den reflektiva `stringify()` och den handskrivna expansionen ovan körs sida vid sida på samma objekt, tillsammans med den reducerade versionen, och programmet jämför resultaten och returnerar felkod om de skiljer sig åt. Det gör de inte.

```cpp
auto e = Employee{"Nisse", 42, 12345.0};

auto reflective = stringify(e);
auto unrolled   = stringify_unrolled(e);
auto reduced    = stringify_unrolled_reduced(e);
auto identical  = (reflective == unrolled) && (unrolled == reduced);

std::println("template for : {}", reflective);
std::println("hand-expanded: {}", unrolled);
std::println("hand-reduced : {}", reduced);
std::println("identical    : {}", identical);
```

```text
template for : Employee {name:std::string = "Nisse", age:int = 42, salary:double = 12345}
hand-expanded: Employee {name:std::string = "Nisse", age:int = 42, salary:double = 12345}
hand-reduced : Employee {name:std::string = "Nisse", age:int = 42, salary:double = 12345}
identical    : true
```

* * *

Att kompilera det här
---------------------

Stödet är i skrivande stund (september 2026) fortfarande experimentellt, men fullt användbart i **GCC 16.1**. Det ligger bakom en flagga:

```bash
g++ -std=c++26 -Wall -freflection my-demo.cxx -o my-demo
```

Glömmer du `-freflection` är GCC vänlig nog att säga det rakt ut, om felet råkar bli en `^^`\-operator: _"reflection is only available with `-freflection`"_. Slår det till på en `using std::meta::...` högst upp i filen istället får du det betydligt trubbigare _"`std::meta` has not been declared"_, upprepat en gång per rad — vilket ser ut som att headern saknas, men inte gör det.

`std::meta` bor i headern `<meta>`. Namnen är långa nog att det lönar sig att plocka in dem med `using`\-deklarationer högst upp i filen, snarare än att skriva ut `std::meta::` framför varje anrop:

```cpp
#include <meta>

using std::meta::identifier_of;
using std::meta::nonstatic_data_members_of;
using std::meta::display_string_of;
using std::meta::type_of;
using std::meta::access_context;
using std::define_static_array;
```

All kod i artikeln är byggd och körd på riktigt, med GCC 16.1, och ligger på GitHub — klona och kör.

* * *

Nästa vecka: från reflection till ORM
-------------------------------------

Den fjärde syntaxbiten, annotationerna, har fått gå i förbigående hittills. Det är för att den förtjänar en egen artikel.

Så här ser målet ut. En helt vanlig struct, med metadata påhängd:

```cpp
namespace schema {
    struct [[=Table{}]] Account {
        [[=Column{.nullable = false}]]
        [[=PrimaryKey{.auto_increment = true}]]
        int id = 0;

        [[=Column{.nullable = false}]]
        std::string customerName;

        [[=Column{.sql_type = "CHAR(34)", .nullable = false}]]
        std::string iban;

        [[=Column{.sql_type = "DECIMAL(12,2)", .default_value = "0.00"}]]
        double balance = 0.0;
    };
}
```

Och så här ser det ut när programmet kör:

```text
CREATE TABLE accounts (
    id INTEGER GENERATED ALWAYS AS IDENTITY NOT NULL,
    customer_name VARCHAR(256) NOT NULL,
    iban CHAR(34) NOT NULL,
    balance DECIMAL(12,2) DEFAULT 0.00,
    PRIMARY KEY (id)
);
```

Inga makron. Ingen kodgenerator som körs som ett separat steg. Ingen registrering av typer i någon förteckning. Tabellnamnet `accounts` och kolumnnamnet `customer_name` stod aldrig skrivna någonstans — de härleddes ur `Account` och `customerName` vid kompilering, och annotationerna säger bara det som avviker från konventionen.

Samtliga programexempel i demokoden, såsom `stringify.cxx` och `sql-generator.cxx`, finns redan på GitHub om du vill tjuvkika. Länken finner du sist i denna artikel.

I den artikeln går jag igenom implementation av ett hyfsat komplett ORM-ramverk kallat _tiny-orm_. Du får se hur samma annoterade struct används både för att läsa CSV-data och generera SQL. Det sistnämnda innebär både att skapa/radera tabeller (CREATE/DROP TABLE), modifiera data (INSERT/UPDATE/DELETE), samt extrahera och aggregera data (SELECT).

Vi går igenom hur annotationer definieras och läses tillbaka, hur man härleder `snake_case`\-namn i `consteval`\-kod, varför `const char*` och `std::string_view` _inte_ överlever resan genom reflection, och hur en felaktig foreign key blir ett kompileringsfel istället för ett runtime-problem.

Missa inte nästa veckas fortsättning då jag visar hur långt det går att dra innan man har en hel liten ORM i modern C++. "_Släng dig i väggen Hibernate_" 😃
