April 25, 2025·6 min read·engineeringcppgcctemplatescpp26

GCC 15 adds pack indexing and more explicit C++ diagnostics

GCC 15.1 implements selected C++26 proposals while changing template diagnostics and library assertions. Pack indexing shows how new syntax can replace indirect metaprogramming.

Extracting one element from a template parameter pack has traditionally required machinery whose purpose is less obvious than the operation itself. GCC 15 implements C++26 pack indexing, which gives that operation direct syntax. The change belongs to a larger release that also diagnoses some template errors earlier and enables libstdc++ assertions in unoptimized builds by default.

The GCC 15.1 release appeared on April 25. Its C++ change summary lists specific C++26 proposals implemented in the compiler and additional experimental library work. That list is evidence for individual capabilities. It is not a claim that GCC implements every feature adopted into the draft standard.

I accept much of C++'s compile-time cost because templates can remove runtime abstraction overhead. The less attractive part is expressing a simple operation through layers of helpers that then appear in diagnostics. Pack indexing is interesting to me because its source expression describes the operation directly, while the compiler work remains at translation time.

Indexing a pack becomes a language operation

Corentin Jabot and Pablo Halpern's P2662R3, dated November 8, 2023, defines the syntax as a pack name followed by an ellipsis and a constant subscript. A type pack yields a type. A pack of expressions yields an expression. The paper motivates the change through the complexity and compiler cost of extracting elements with deduction or metaprogramming libraries.

A small type alias illustrates the operation. This uses the C++26 syntax GCC 15 implements, so it belongs in the appropriate experimental language mode.

template<class... Types>
using first_type = Types...[0];

using selected = first_type<int, double>;

Here selected is int. The index selects an element from the sequence of template arguments. It does not index a runtime array, and an empty pack has no element zero to select. The constant index is part of the compile-time operation.

The paper explains why the ellipsis placement matters. Selecting one array from a pack of arrays is different from subscripting each array during a pack expansion. The syntax has to distinguish those operations. Reusing square brackets alone would leave ambiguity between indexing the pack and indexing its elements.

That is more than a notation preference. Existing templates can already have array-like elements and separate packs of indices. A new operator must fit those expressions without making their intended expansions harder to identify. P2662 discusses alternatives in that context, then chooses the pack...[index] form implemented here.

The proposal also discusses possible future extensions. Those discussions do not mean GCC 15 has acquired slicing or a universal object-indexing facility. The implemented feature named in the release notes is pack indexing. Keeping that boundary prevents the motivation section of a proposal from becoming an inaccurate implementation checklist.

Deleted functions can explain their reason

GCC 15 implements P2573R2, which permits a string reason on a deleted function. The release summary identifies the syntax as = delete("reason");. This gives an intentionally unavailable operation a place to explain its design restriction in the declaration.

struct Resource {
    Resource(const Resource&) = delete("resource ownership cannot be copied");
};

The example does not claim that deleting the copy constructor is new. The addition is the accompanying message. A diagnostic for using the forbidden operation can therefore refer to the reason attached to it, instead of leaving the reader to infer the author's intent from the missing operation alone.

This is a useful counterpart to pack indexing. One change shortens the expression of a permitted template operation. The other gives an intentionally prohibited operation a specific explanation. Both affect how much design intent survives into compiler feedback.

They remain C++26 implementation work in this release. A project supporting older language modes or compilers still has to account for that syntax requirement in public headers. A declaration that is easy for a human to understand does not become valid in every compiler merely because its semantics are simple.

Some template errors appear earlier

The GCC summary says qualified lookup failures into the current instantiation are now proactively diagnosed during template parsing. It gives a name such as this->non_existent as an example. A template can therefore produce a diagnostic before a later instantiation path would have exposed the problem.

The release adds -Wtemplate-body to control diagnosing errors while parsing a template. That option accompanies a change in when the compiler reports certain invalid definitions. It should not be described as a new template language rule that makes every dependent name resolvable before instantiation.

Earlier diagnostics can change the experience of upgrading a project even when the affected template was not used by the final program. The source still contains a definition that GCC can now examine more precisely. The useful response depends on the invalid construct, rather than assuming every new diagnostic is caused by adopting C++26 syntax.

GCC also introduces an experimental nested presentation for complicated diagnostics using -fdiagnostics-set-output=text:experimental-nesting=yes. The release explicitly marks that presentation as subject to change. It is a display facility for understanding errors, separate from which source programs the compiler accepts.

I find that separation useful when evaluating a compiler upgrade. Better messages can reduce the effort of understanding an existing constraint, while an earlier error can reveal that the source relied on a diagnostic gap. Neither implies a runtime behavior change for every successfully compiled program.

Library assertions change unoptimized builds

libstdc++ now enables debug assertions by default in unoptimized builds. The release documents _GLIBCXX_NO_ASSERTIONS as the override. This is a library configuration change with a narrower scope than saying that every GCC 15 program has comprehensive runtime checks.

Optimization settings consequently affect the default assertion configuration described by the release. A developer can encounter a newly diagnosed invalid library use in an ordinary unoptimized build. That observation does not establish the assertion policy of a separately built optimized deployment.

The change also should not be equated with implementing the complete C++26 standard library hardening specification. The release summary states its own assertion policy. Standardization and a particular library's configuration are related topics, but their requirements need to be identified separately.

For an existing project, the immediate implication is that the compiler upgrade can alter development-time failure behavior without a source edit. Assertions that were previously opt-in may now run in that build configuration. The program's obligation to satisfy the underlying library requirements remains the same.

Compile-time library use continues to expand

The libstdc++ section lists experimental C++26 support for constant-evaluated sorting and raw-memory algorithms. That is related to the compiler's implementation of constexpr placement new, P2747R2, but the compiler and library entries remain distinct. Accepting an expression in constant evaluation and supplying a library algorithm that uses it are separate implementation tasks.

The same library section identifies std::is_virtual_base_of and several new range views, including views::concat. These names give users a specific implementation status to investigate. They do not imply that every facility appearing in a current C++26 overview is available in this release.

For C++23, GCC lists std::flat_map and std::flat_set as well as container range constructors and insertion functions. This is another reason a single standard-version label is insufficient to describe a toolchain. GCC 15 advances support for a published revision while it implements selected pieces of the next one. A library header can depend on either kind of work, with different portability implications for callers.

Allocation replacements have a documented assumption

Another release change concerns replaceable global allocation functions. GCC adds -fassume-sane-operators-new-delete and enables it by default. The summary says a program whose replacement definitions read or modify global state visible elsewhere may need -fno-assume-sane-operators-new-delete.

This is relevant to projects with custom allocation instrumentation because the compiler's optimization assumptions and the replacement function's observable effects must agree. The release notes do not say every replacement allocator is invalid. They identify the state interaction that can require a different compilation choice.

That distinction follows the same principle as the template and assertion changes. A compiler version is not one indivisible semantic switch. Its language mode, optimization assumptions, and library configuration contribute separate requirements. GCC 15's documented additions make those requirements concrete enough to discuss individually.

The pack-indexing addition is the clearest source-level example. A template can now name an element of its pack directly through the implemented C++26 operation. The surrounding release improves how other template and library obligations become visible, but the adoption boundary for that new expression remains the compiler's documented support for the proposal and the selected language mode.