Representative interview topic

C++ coding interview: How do you evaluate C++26 static reflection without treating a proposal as a stable ABI?

CodingHard
Offer.cc Editorial TeamPublished Updated

Question

A team wants C++26 static reflection to generate enum strings and serialization code. Explain P2996's compile-time model, why it is not runtime reflection or a stable ABI promise, and how you would design fallbacks, tests, and a release matrix.

Prompt and context

A team wants C++26 static reflection to remove hand-written branches for enum names, configuration checks, and serialization. The candidate must explain the WG21 P2996R13 model, how compile-time meta-information participates in template instantiation, and how to ship while compiler support is incomplete. A strong answer separates the language proposal, generated code, and ABI compatibility.

What the interviewer is testing

  • Whether the candidate knows P2996 is a WG21 proposal, not a universal stable compiler feature.
  • Whether they distinguish compile-time reflection, runtime type information, and text macros.
  • Whether they can explain reinjecting reflected entities into declarations, including access control and instantiation cost.
  • Whether they recognize generated layout, symbol, and cross-compiler ABI risks.
  • Whether they can propose a testable fallback instead of presenting syntax as the solution.

Clarifying questions to ask first

  1. Which compiler, standard library, language level, and feature switches are supported?
  2. Is generated code used only at build time, or must the process load unknown types at runtime?
  3. Must the artifact stay stable across compilers, shared libraries, or languages?
  4. Should reflection expose read-only metadata, or generate member access, serialization, and validation functions?
  5. What budgets exist for build time, binary size, and diagnostics?

A 30-second answer

“P2996R13 describes compile-time static reflection: a reflection value enumerates types and members during compilation, then splicing syntax can generate declarations. It is not a runtime scanner and does not promise a cross-compiler ABI. I would lock a compiler support matrix, keep reflection inside a source-generation boundary, retain handwritten or generated fallbacks, and test semantics, layout, and build cost. Public ABI remains an explicit interface; implementation details must not leak through reflected layouts.”

Step-by-step deep answer

1. Separate three reflection models

Runtime reflection queries types while a program runs. RTTI exposes a limited form of dynamic type information. Static reflection treats metadata as a compile-time entity. P2996 aims to let the compiler traverse types, members, and attributes during constant evaluation and produce ordinary C++ declarations. It cannot make an already deployed binary discover a new class.

2. Show the proposal’s intent with syntax

The following is conceptual; the exact implementation must match the proposal revision supported by the compiler. ^^ obtains reflection information and [: ... :] splices a reflection result back into a declaration. These tokens must not be presented as production syntax accepted by every toolchain.

cpp
enum class Color { red, green, blue };

consteval auto names() {
  constexpr auto r = ^^Color;
  // Pseudocode: enumerate members and build a compile-time string table.
  return make_enum_name_table(r);
}

constexpr auto color_names = names();

The key interview point is the data flow: the compiler creates metadata, templates or constant functions process it, and the final artifact is still ordinary static data and functions.

3. Explain declaration splicing and access boundaries

Splicing can place a reflected type or member back into a declaration, but it does not bypass access control, lifetime, or type checking. Generated member access still obeys private, protected, base-class, and name-lookup rules. Turning an inaccessible private member into a public serialized field changes the security contract.

4. Estimate template and build cost

A large type graph can instantiate reflection logic repeatedly in many translation units, increasing build time and diagnostic noise. Keep reflection behind one generation boundary, cache generated tables, and measure peak memory with incremental builds. Do not wrap every business template in reflection until it proves that duplicate code and maintenance cost actually fall.

5. Keep ABI separate from generated code

Reflected field order, names, and layout can change with the compiler, standard library, or proposal revision. Cross-shared-library interfaces should use stable DTOs, versioned serialization, and explicit symbols; reflection should generate implementation-side adapters. Changing a private member must not accidentally change the public ABI.

6. Design a compiler fallback matrix

Use capability detection or build options to select reflection and handwritten implementations, but make both share the same behavior tests. CI should cover an experimental compiler with the proposal support, the stable toolchain, and a build with reflection disabled. Record compiler and library versions, feature switches, generation hashes, and binary-interface checks in each artifact.

High-quality sample answer

“I would evaluate P2996R13 as a compile-time language capability. It turns types and members into compile-time metadata and uses templates and splicing to produce ordinary declarations; it is not a runtime class scanner and does not solve cross-compiler ABI. The ^^ and splicing example must be validated only on a toolchain that explicitly supports the proposal revision. In production I would keep reflection behind an internal generation boundary, use stable DTOs and versioned formats for public interfaces, and retain a handwritten fallback. The test matrix compares enum names, serialized bytes, error behavior, build time, and public symbols before enabling it gradually.”

Common mistakes

  • Calling the proposal a broadly shipped standard → compiler support and revisions differ → pin versions and keep a fallback.
  • Treating static reflection as a runtime plugin system → compile-time metadata cannot discover post-deployment types → use an explicit registration protocol for plugins.
  • Letting reflection define public layout → member changes can contaminate ABI → isolate generated implementation code behind stable DTOs.
  • Ignoring access control → a generator cannot legally read arbitrary private members → make field exposure an explicit trait or policy.
  • Looking only at source-line reduction → template instantiation can slow builds → measure time, memory, and binary size.

Follow-ups and responses

How is this different from macro-generated code?

Macros replace tokens lexically and lack type semantics and normal name lookup. Static reflection processes metadata after the compiler understands the type, so it can reuse the type system and constant evaluation. It still depends on compiler support and build cost, and neither mechanism creates runtime extension by itself.

How would you generate an enum-to-string map?

Reflect the enum members and generate an array or lookup table at compile time, with an explicit unknown branch for invalid underlying values. Test every member, out-of-range integers, duplicate-name policy, and the fallback result; stringification is not protocol compatibility.

How would you verify cross-compiler consistency?

Feed generated output into the same golden tests and compare serialized bytes, error codes, and public symbols rather than internal metadata representation. Pin the proposal revision per compiler, and fall back to handwritten code with a visible warning when the matrix fails.

When should you avoid static reflection?

Avoid it when runtime loading of unknown modules is required, public ABI must remain long-lived, the toolchain cannot be pinned, or reflection saves a few lines but materially increases build cost. Explicit registration, a code generator, or a handwritten adapter is easier to audit.

Public sources

Related questions

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool