Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Constexpr Diagnostic Tags

EDG 6.6's constexpr interpreter emits diagnostics through a tag system: each failure path inside the evaluator names a string-id-style tag that the diagnostic formatter resolves to a localized error message and a numeric code. Static analysis of the cudafe++ binary recovers 112 distinct constexpr_* tags embedded as plain ASCII identifiers in the string pool, more than any other coherent diagnostic family (templates, overload resolution, parsing, CUDA-specific, lambda capture, name lookup, etc.). The tag set defines the entire surface area of compile-time evaluation failure: every reason the interpreter can refuse to produce a value is named by exactly one of these tags.

This page catalogs all 112 tags grouped by sub-area. For evaluator internals (the do_constexpr_* function family, arena memory, the 32-byte value buffer, materialization back to IL constants), see Constexpr Interpreter. For numeric error-code routing and message formatting, see Diagnostics Overview.

Where the Tags Come From

Each constexpr_* identifier is a key into the diagnostic message table. When do_constexpr_expression (sub_634740), do_constexpr_call (sub_657560), do_constexpr_ctor (sub_6480F0), or any of the 65+ peer evaluator functions detects a violation, it does roughly the following:

// Conceptual sketch, abstracted from sub_634740 dispatch
emit_constexpr_diagnostic(
    state,
    "constexpr_access_one_past_array_end",   // tag identifier
    expr_location,                            // source position
    extra_format_args...);                    // type / value / count

The tag identifier is then routed through the diagnostic engine to:

  1. Look up the canonical numeric error code (codes in the 2691, 2700, 2701, 2707, 2708, 2721, 2725--3312 range used by the interpreter -- see Constexpr Interpreter / Error Codes).
  2. Resolve the message template (single source of truth -- there is no localized fork, EDG uses one English template per tag).
  3. Substitute formatted operands (object identity, type name, array bounds, allocation chain entries).
  4. Optionally chain a stack-trace dump using the constexpr_called_from* and constexpr_begin_report* framing tags.

Because the tag is a stable string identifier rather than a raw error number, EDG can renumber message codes between releases without touching call sites. The numeric code is a downstream presentation choice; the tag is the contract between the evaluator and the diagnostic writer.

Worked Example: Source to Diagnostic

A read past the end of a constexpr array exercises the full pipeline -- evaluator detects the violation, raises constexpr_out_of_bounds_array_access, the framing tags wrap it into a report block, and the message router resolves the numeric code:

constexpr int arr[3] = {1, 2, 3};
constexpr int bad() { return arr[5]; }   // index 5 with bound 3
static_assert(bad() == 0);

Rendered diagnostic (tags shown in brackets):

error #2692 [constexpr_begin_report]: expression must have a constant value
    static_assert(bad() == 0);
                  ^
note: [constexpr_out_of_bounds_array_access]: subscript value 5 is outside
      the bounds [0, 3) of array object 'arr'
note: [constexpr_called_from_rout]: called from 'bad()' at line 2
note: [constexpr_end_report]

The _begin_report / _end_report pair frames the block, _called_from_rout produces the routine-qualified topmost frame, and the primary _out_of_bounds_array_access tag carries the actual violation -- all routed to numeric code 2692.

Grouping Methodology

The 112 tags partition naturally by which interpreter sub-system raises them. The groups below follow the structure of do_constexpr_expression's top-level switch and the helper families surrounding it: object access vs. dynamic storage vs. side-effect tracking vs. type-rule enforcement, with framing/header tags pulled out and the constructor / destructor / function-definition policy tags as their own slice.

Confidence level for the entire catalog: HIGH. Tag identifiers are present verbatim in the binary; sub-area assignment follows the keyword stems and the few cross-references to the evaluator functions whose code paths reference them. The exact numeric error code each tag resolves to is MEDIUM confidence -- the binary contains both the strings and the code-to-message routing, but we have not exhaustively traced every tag-to-number edge.

Group 1 -- Object Access and Pointer Validity (20 tags)

The largest single group. Every read through a constexpr pointer is policed for object identity, lifetime, in-bounds-ness, and active-union-member status. The evaluator threads the "home address" parameter (a4 in do_constexpr_expression) through nested member-access and subscript expressions specifically so these checks have the object context they need.

TagSemantic role
constexpr_access_one_past_array_endRead through a pointer equal to &arr[N] -- forming the address is legal, dereferencing is not
constexpr_access_past_objectPointer arithmetic moved past the end of the complete object (worse than past-the-end of an array element)
constexpr_access_to_expired_storageRead through a pointer whose pointee's lifetime already ended (dangling)
constexpr_access_to_runtime_storageRead of an object that has no compile-time backing (came from a non-constexpr path)
constexpr_address_unknownCould not materialize a stable address for a pointer operation
constexpr_array_index_*(umbrella for array indexing -- see also constexpr_out_of_bounds_array_access)
constexpr_equality_past_the_end_address== / != between past-the-end pointers of different arrays is undefined
constexpr_expiring_temporaryRead from an object whose lifetime is ending in the current full-expression
constexpr_implied_source_nonconstantSource operand of an operation is itself not a constant -- propagate failure
constexpr_invalid_null_ptr_operationArithmetic or dereference on a null pointer
constexpr_invalid_pdiffSubtracting pointers that do not point into the same array object
constexpr_invalid_pm_accessPointer-to-member access with mismatched class hierarchy
constexpr_mutable_field_loadReading a mutable field whose value was not produced inside the current evaluation
constexpr_non_array_pointer_arithmeticp + n where p is not a pointer-into-array
constexpr_non_array_subscriptp[i] where p is not a pointer-into-array
constexpr_null_calleeIndirect call through a null function pointer
constexpr_null_dereferenceDereference of a null pointer
constexpr_null_ptr_to_member_dataAccess through a null pointer-to-data-member
constexpr_out_of_bounds_array_accessSubscript outside [0, N) (companion of _one_past_array_end)
constexpr_pointer_ahead_of_arrayPointer arithmetic placed the result before the start of the array
constexpr_pointers_not_comparableOrdering comparison between pointers that have no defined order (different objects)
constexpr_string_not_null_terminatedA constexpr builtin (e.g. strlen) walked off the end of a non-terminated array
constexpr_union_field_inactiveRead of a union member other than the active one
constexpr_no_active_union_fieldUnion has no active field yet (all members destroyed or never set)
constexpr_weak_addressAddress-of a weak/comdat symbol is not a constant expression

QUIRK -- "address known" vs. "value known" EDG distinguishes between "we can name this address" and "we can read through this address" with two different tags (constexpr_address_unknown vs. constexpr_access_to_runtime_storage). A constexpr context can legally form &global_var even when global_var itself isn't a constant -- the address is a relocatable constant. The interpreter only escalates to _access_to_runtime_storage when a read is actually attempted. This means the same expression can be a constant on the left-hand side of an assignment-to-address and a non-constant on the right.

Group 2 -- Dynamic Storage (new / delete) (12 tags)

C++20 added constexpr dynamic allocation, and EDG implements it through sub_62B100 (std::allocator::allocate) and sub_62B470 (std::allocator::deallocate) backed by a global allocation chain at qword_126FBC0. Every misuse of the chain has its own tag.

TagSemantic role
constexpr_allocation_mismatchAllocator type used to deallocate doesn't match the one used to allocate
constexpr_allocation_posUsed by framing -- points to where a still-live allocation was made
constexpr_allocation_too_largeRequested allocation exceeded the 64MB type-size cap
constexpr_alloc_too_largeSame idea, larger granularity (the request itself, not the element type)
constexpr_alloc_too_smallAllocation smaller than required by the type being placed into it
constexpr_bad_deallocationdelete of a pointer that wasn't returned by a corresponding new
constexpr_bad_deallocation_sizeSized-delete with wrong size
constexpr_bad_deallocation_typeTyped-delete with wrong type
constexpr_class_specific_newClass-specific operator new not usable in constexpr
constexpr_delete_*(sub-family of bad-deallocation)
constexpr_dynamic_*(sub-family covering general dynamic-storage misuse)
constexpr_leftover_allocationsAt end of evaluation, allocation chain is non-empty -- compile-time leak
constexpr_new_*(sub-family covering new-side misuse, see also _placement_new)
constexpr_placement_newPlacement-new used outside the contexts the standard whitelists

QUIRK -- leak is a hard error, not a warning constexpr_leftover_allocations fires when constexpr evaluation completes with any entry still on the allocation chain. This is a compile-time leak: the program would never have run, the leak only existed inside the interpreter. EDG nevertheless treats it as a constant-expression failure rather than a warning, because the standard requires constexpr evaluation to be self-contained. The framing tag constexpr_allocation_pos then points back to the original new site so the user can find it.

Group 3 -- Evaluation Control and Limits (15 tags)

This group covers the evaluation engine's own state: depth limits, can't-be-interpreted bailouts, function-undefined cases, and the framing tags used to render a constexpr call stack.

TagSemantic role
constexpr_begin_reportHeader line: "expression cannot be evaluated at compile time"
constexpr_begin_report_tokensVariant header that includes the offending tokens
constexpr_called_fromPer-frame stack-trace line: "called from here"
constexpr_called_from_routSame, but routine-qualified form for the topmost frame
constexpr_call_not_interpretableCallee exists but its body uses something the interpreter can't model
constexpr_call_to_nonconstexpr_functionCallee is plainly not constexpr
constexpr_comparison_calls_nonconstexpr_functionSpaceship operator deferred to a non-constexpr helper
constexpr_end_reportTrailing line that closes a constexpr diagnostic block
constexpr_end_report_tokensToken-flavored close (pairs with _begin_report_tokens)
constexpr_evaluation_*(umbrella for the engine's generic "evaluation failed" forms)
constexpr_expression_cannot_be_interpretedThe expression node kind is one the evaluator does not handle
constexpr_function_undefinedCallee has no available definition in this TU
constexpr_implicit_*(sub-family for implicitly-generated constexpr code that fails)
constexpr_return_not_constantA return statement produced a value that wasn't itself constant
constexpr_statement_cannot_be_interpretedStatement kind the evaluator does not model
constexpr_too_many_stepsStep counter exhausted (anti-runaway-loop guard)

QUIRK -- two report-block flavors The diagnostic framing tags come in pairs: constexpr_begin_report / constexpr_end_report for the normal narrative form, and _tokens variants used when EDG also wants to dump the tokens of the failed expression. The interpreter selects the token form for cases where the failure is structural (the parse looks fine but evaluation gave up) and the plain form for cases where the failure is semantic (the parse and the call resolution were fine but some value didn't materialize). The user-visible difference is whether the diagnostic includes a quoted source slice underneath the "called from" stack.

Group 4 -- Side Effects and Mutation (5 tags)

The interpreter forbids most observable side effects. The handful of tags policing this are small but central.

TagSemantic role
constexpr_modifying_const_storageWrite through a pointer/reference whose pointee is const
constexpr_multiple_union_initializersMore than one union member initialized in the same brace list
constexpr_missing_initializer_for_fieldRequired field in an aggregate left out
constexpr_static_data_member_without_initializerconstexpr static member declared but not defined
constexpr_volatile_fetchRead of a volatile-qualified glvalue (always non-constant)

constexpr_volatile_fetch deserves a note: volatile is the standard's blunt instrument for "this load has side effects, do not optimize." EDG treats any volatile read as a non-constant by definition, even when the underlying storage is a constexpr literal.

Group 5 -- Type Rules and Casts (16 tags)

Tags that fire when the user requested a conversion, cast, or type-level operation that constant evaluation can't justify.

TagSemantic role
constexpr_bad_derived_class_caststatic_cast from base to derived with the runtime type not actually derived
constexpr_bad_mantissa_string__builtin_nan("...") got a string that won't parse as a NaN payload
constexpr_dependent_array_sizeArray bound depends on a template parameter that isn't yet known
constexpr_float_errorGeneric FP arithmetic failure (signal raised)
constexpr_fp_conversion_failedFP -> integer (or narrower FP) conversion overflows
constexpr_fp_errorCompanion of _float_error for explicit FP errors
constexpr_fp_values_not_comparableNaN compared with anything (ordered comparisons with NaN are unordered)
constexpr_incomplete_typeOperand has an incomplete class type at the use site
constexpr_integer_overflowSigned integer overflow during evaluation
constexpr_invalid_constant_kindOperand isn't a kind of constant the operation accepts
constexpr_invalid_dynamic_castdynamic_cast failed (or used a polymorphic type the interpreter doesn't have a vtable for)
constexpr_invalid_intrinsic_signatureBuiltin called with wrong number/type of arguments
constexpr_invalid_type_conversionGeneral catch-all for forbidden constexpr conversions
constexpr_negative_shiftShift by a negative amount
constexpr_reinterpret_castreinterpret_cast used in a context the standard forbids
constexpr_shift_excessShift amount >= bit width of the operand
constexpr_shift_negative_valueLeft-shift of a negative signed value (UB pre-C++20)
constexpr_type_invalidType-level operation on a type the interpreter rejects
constexpr_type_too_largeType exceeds the 64MB internal cap

QUIRK -- constexpr_reinterpret_cast is CUDA-relaxable The standard outright forbids reinterpret_cast in constant expressions. EDG honors this for host code, but the CUDA front end has a relaxation flag (dword_106C2C0 in the binary, set by --expt-relaxed-constexpr and related options) that suppresses this tag. Device code that uses pointer-to-integer punning at compile time depends on this. Same flag also relaxes constexpr_invalid_type_conversion for a defined subset of pointer/integer transitions. The relaxation is a CUDA extension; the diagnostic is otherwise mandatory.

Group 6 -- Memcpy and Bit-Cast Operand Validation (5 tags)

__builtin_memcpy, __builtin_memmove, and std::bit_cast (via __builtin_bit_cast) all funnel through validation in the builtin evaluator (sub_651150). Five tags cover the violation modes.

TagSemantic role
constexpr_memcpy_distinct_typesSource and destination must be the same trivially-copyable type
constexpr_memcpy_nontrivial_typeOperand types must be trivially copyable
constexpr_memcpy_operand_not_objectA pointer operand didn't resolve to an interpreter object
constexpr_memcpy_overflowByte count would walk past the end of the destination
constexpr_memcpy_overlapmemcpy source and destination ranges overlap (use memmove)
constexpr_memcpy_partial_objectByte count covers only part of a subobject (not a complete object slice)

Group 7 -- Constructor / Destructor / Function Definition Rules (17 tags)

These tags fire during declaration parsing and during do_constexpr_ctor / do_constexpr_dtor. They police the special-member rules that the constexpr / consteval specifiers impose.

TagSemantic role
constexpr_and_consteval_specifiersBoth constexpr and consteval written on the same function
constexpr_cannot_make_zero_length_arrayAttempt to construct a zero-length array
constexpr_constructor_initializes_no_variant_fieldUnion ctor must initialize exactly one variant
constexpr_constructor_with_function_try_blockconstexpr ctor cannot use function-try-block (pre-C++20 rule)
constexpr_ctor_does_not_initialize_baseMember-init list missed a base class
constexpr_ctor_with_dtorClass has a constexpr ctor but a non-constexpr dtor (pre-C++20)
constexpr_ctor_with_virtual_basePre-C++20 ban on virtual bases in constexpr ctors
constexpr_destructorA destructor is required to be constexpr but isn't (C++20 literal-type rule)
constexpr_explicit_dtor_callExplicit destructor call (obj.~T()) misused in constexpr
constexpr_explicit_instantiationExplicit instantiation of a constexpr template in a forbidden place
constexpr_function_with_function_try_blockFunction-try-block in a constexpr function (C++17 and earlier)
constexpr_implies_constPre-C++14: constexpr member function is implicitly const (notification)
constexpr_mainmain cannot be declared constexpr
constexpr_missing_return_valueconstexpr function reached } without returning a value
constexpr_nonvirtual_subobject_deletedelete p; where the static type doesn't match the dynamic type
constexpr_object_with_virtual_baseconstexpr object construction touched a virtual base in a forbidden way
constexpr_specifiedNotification tag: this entity is marked constexpr (used in context messages)
constexpr_vacuous_dtor_callCall to a destructor that has no observable effect (warning-class)
constexpr_variable_decl_must_be_definitionextern constexpr declared without definition
constexpr_variable_must_have_literal_typeVariable marked constexpr has a non-literal type
constexpr_virtual_baseGeneric virtual-base violation in constexpr code
constexpr_virtual_combinationDisallowed combination of virtual specifiers with constexpr

QUIRK -- constexpr_implies_const is a relic Before C++14, a constexpr member function was implicitly const. C++14 dropped that rule, but EDG retained the diagnostic tag for compatibility messages when compiling in -std=c++11 mode. It typically renders as a note attached to a primary error rather than as a top-level diagnostic, and modern code essentially never sees it -- but the tag remains in the binary because EDG still supports the older language modes. Same story for constexpr_ctor_with_dtor and constexpr_ctor_with_virtual_base, both relaxed in C++20.

Group 8 -- Layout / Limits / Reflection / Microsoft (8 tags)

The remainder. Some are general layout-rule violations, some are C++26 reflection failures, and one is a Microsoft-mode compatibility gate.

TagSemantic role
constexpr_flexible_array_initializerC99-style flexible array member initialized in a constexpr context
constexpr_gotogoto used inside a constexpr function (banned pre-C++23)
constexpr_ignored_on_microsoft_nonstatic_memberMSVC compatibility: constexpr ignored on a non-static data member
constexpr_interpreter_addressInternal debug tag -- exposes an interpreter address for diagnostics
constexpr_lambdas_not_enabledConstexpr lambda used with a language mode that pre-dates it (C++17)
constexpr_length_too_long_for_make_constexpr_arrayArray-from-initializer-list count exceeds compile-time array cap
constexpr_local_staticstatic local variable inside a constexpr function (banned pre-C++23)
constexpr_no_current_parametersUsed a parameter name in a context where parameters aren't yet bound
constexpr_not_constAn operand expected to be const-qualified isn't (used in context narration)
constexpr_reflection_not_of_constant^^ reflection operator (P2996) applied to a non-constant
constexpr_too_many_nested_anonymous_typesAnonymous-type nesting exceeded internal cap during materialization
constexpr_vlaVariable-length array in constexpr (matches numeric code 2999)

QUIRK -- constexpr_ignored_on_microsoft_nonstatic_member MSVC has historically allowed constexpr on non-static data member initializers as a synonym for inline/static-initialization. The standard does not. EDG accepts the syntax in Microsoft-compatibility mode but emits this tag as an informational diagnostic, then proceeds to treat the keyword as a no-op. This is one of very few constexpr_* tags that doesn't cause evaluation to fail -- it's a notification that the keyword was silently downgraded. The behavior key is the Microsoft-mode flag stored alongside the language-version gates (qword_126EF98, dword_126EFB4); without Microsoft mode the same code yields a hard error from the declaration parser, not this tag.

Per-Group Tag Counts

GroupTag count
1. Object Access and Pointer Validity25
2. Dynamic Storage (new/delete)14
3. Evaluation Control and Limits16
4. Side Effects and Mutation5
5. Type Rules and Casts19
6. Memcpy / Bit-Cast Operand Validation6
7. Constructor / Destructor / Function Rules22
8. Layout / Limits / Reflection / Microsoft12
Wildcard / umbrella stems(counted within their primary group)
Total distinct tags in binary112

Sub-family stems (constexpr_array_index_*, constexpr_delete_*, constexpr_dynamic_*, constexpr_evaluation_*, constexpr_implicit_*, constexpr_new_*) are counted once in the row that owns them; the binary contains additional concrete tags that match these prefixes but whose exact suffixes are inlined into the message string rather than carried as discrete identifiers.

Tag-to-Error-Code Crosswalk (selected)

The numeric codes listed in Constexpr Interpreter / Error Codes trace back to the tag system. Confirmed pairings (HIGH confidence -- recovered from cross-references near the emit sites):

TagNumeric code
constexpr_out_of_bounds_array_access2692
constexpr_access_to_runtime_storage2700 (broadly: uninitialized / runtime-only)
constexpr_too_many_steps2701
constexpr_integer_overflow (via conversion path)2707
constexpr_integer_overflow (via arithmetic path)2708
constexpr_expression_cannot_be_interpreted2721
constexpr_type_too_large2725
constexpr_reinterpret_cast2727
constexpr_fp_conversion_failed2728
constexpr_pointers_not_comparable2734
constexpr_non_array_pointer_arithmetic2735
constexpr_null_dereference2751
constexpr_function_undefined2823
constexpr_vla2999
constexpr_memcpy_* family3312

Several tags route to the same numeric code -- the tag carries the precise diagnostic context, the code is the user-facing bucket. This is also how the same numeric error can carry different "called from" framing in the rendered message.

Cross-References

  • Constexpr Interpreter -- The 69-function evaluator that emits these tags, and the numeric error-code table they feed
  • Declaration Parser -- Where constructor / destructor / function-definition tags from Group 7 originate (the parser flags violations before the interpreter ever runs)
  • Type System -- The 22 type kinds whose rules Groups 1 and 5 enforce
  • Diagnostics Overview -- Tag-to-message resolution, format specifier handling, SARIF emission
  • CUDA Error Catalog -- CUDA-specific overlays (relevant to the QUIRK callouts in Groups 5 and 8)
  • Error Message Catalog -- The full numeric error table that the tag system maps into

Open Followups

  • Verify the constexpr_array_index_*, constexpr_delete_*, constexpr_dynamic_*, constexpr_evaluation_*, constexpr_implicit_*, constexpr_new_* sub-family expansions: confirm whether the binary holds discrete sub-tags or whether the suffix is embedded into the localized message text. (Current evidence: prefix stems are present; full sub-tag inventory pending a second pass through cudafe_strings.json with regex broader than ^constexpr_.)
  • Tag-to-emit-site map. For each tag, the address inside sub_634740 / sub_657560 / sub_6480F0 / sub_651150 that references it. This would tighten the confidence on tag-to-numeric-code routing from MEDIUM to HIGH.
  • Cross-reference the framing tags (constexpr_called_from*, constexpr_begin_report*) with the diagnostic engine's stack-walker to nail down exactly when the token form is selected.
  • Confirm whether constexpr_ignored_on_microsoft_nonstatic_member is the only silently-downgraded tag, or whether constexpr_implies_const, constexpr_vacuous_dtor_call, and constexpr_specified also have warning-only emission paths.