The next major version of the GNU Compiler Collection (GCC), 15.1, is expected to be released in April or May 2025. Like every major GCC release, this version will bring many additions, improvements, bug fixes, and new features. GCC 15 is already the system compiler in Fedora 42. Red Hat Enterprise Linux (RHEL) users will get GCC 15 in the Red Hat GCC Toolset. It's also possible to try GCC 15 on Compiler Explorer and similar pages.
Like my previous article, New C++ features in GCC 14, this article describes only new features implemented in the C++ front end; it does not discuss developments in the C++ language itself.
The default dialect in GCC 15 is still -std=gnu++17
. You can use the -std=c++23
or -std=gnu++23
command-line options to enable C++23 features, and similarly for C++26 and others.
Warning
Note that C++20, C++23, and C++26 features are still experimental in GCC 15. (GCC 16 plans to switch the default to C++20.)
C++26 features
C++26 features in GCC 15 include pack indexing, attributes for structured bindings, enhanced support for functions whose definition consists of =delete
, and more.
Pack indexing
C++11 introduced variadic templates which allow the programmer to write templates that accept any number of template arguments. A pack can represent a series of types or values. For example, to print out arbitrary arguments, one could write:
template<typename T, typename... Types>
void print (T t, Types... args)
{
std::cout << t << '\n';
if constexpr (sizeof...(args) > 0)
print (args...);
}
int main ()
{
print ('a', "foo", 42);
}
However, it was not possible to index an element of a pack, unless the programmer resorted to using various recursive tricks which are generally slow to compile. With this C++26 feature, to index a pack one can write pack...[N]
(where N
has to be a constant expression). A pack index then behaves exactly as if the resulting expression was used. An empty pack cannot be indexed. The following program will print a
:
template<typename... Types>
void print (Types... args)
{
std::cout << args...[0] << '\n';
}
int main ()
{
print ('a', "foo", 42);
}
Attributes for structured bindings
This proposal allows you to add an attribute that appertains to each of the introduced structured bindings, as in the following example:
struct S { int a, b; };
void
g (S& s)
{
auto [ a, b [[gnu::deprecated]] ] = s;
}
=delete with a reason
C++11 provided support for deleted functions; that is, functions whose definition consists of =delete
. Deleted functions participate in overload resolution, but calling them is an error. This replaced the old mechanism of declaring special member functions as private
.
In C++26, it is possible to provide a message explaining why the function is marked as deleted: =delete(“reason”)
. The following program:
void oldfn(char *) = delete("unsafe, use newfn");
void newfn(char *);
void g ()
{
oldfn ("bagel");
}
will cause the compiler to emit:
q.C: In function ‘void g()’:
q.C:7:9: error: use of deleted function ‘void oldfn(char*)’: unsafe, use newfn
7 | oldfn ("bagel");
| ~~~~~~^~~~~~~~~
q.C:1:6: note: declared here
1 | void oldfn(char *) = delete("unsafe, use newfn");
| ^~~~~
Variadic friends
The following feature makes it possible to use a friend
declaration with a parameter pack:
template<class... Ts>
class Foo {
friend Ts...;
};
An example where this feature can be used in practice is the Passkey
idiom:
template<typename... Ts>
class Passkey {
friend Ts...;
Passkey() {}
};
class A;
class B;
struct Widget {
// Only callable from A and B.
void secret (Passkey<A, B>);
};
class A {
void doit (Widget& w) {
w.secret ({}); // OK
}
};
class B {
void doit (Widget& w) {
w.secret ({}); // OK
}
};
class D {
void doit (Widget& w) {
w.secret ({}); // won't compile!
}
};
constexpr placement new
C++20 added support for using new
in a constexpr
context:
constexpr void use (int *) { }
constexpr int
foo ()
{
auto *p = new int[]{1, 2, 3};
use (p);
delete[] p;
return 1;
}
int main ()
{
constexpr auto r = foo ();
}
GCC implemented the proposal in GCC 10. However, constexpr placement new
was not yet possible. C++26 rectifies that situation, and it allows the programmer to write code like:
#include <memory>
constexpr int foo ()
{
std::allocator<int> alloc;
auto p = alloc.allocate (16);
new (p) int(42);
alloc.deallocate (p, 16);
return 1;
}
int main ()
{
constexpr int r = foo ();
}
Structured binding declaration as a condition
The structured bindings feature was added in C++17, and GCC has supported it for a long time. GCC 15 implements P0963R3, which allows structured bindings declaration in if
/while
/for
/switch
conditions; previously, this wasn’t possible.
If a structured binding is used in a condition context, its decision variable is the underlying artificial variable created by the compiler. This variable must be convertible to bool
(except when used in a switch
statement). For example:
struct S {
int a, b;
explicit operator bool () const noexcept { return a != b; }
};
void use (int, int);
void g (S s)
{
if (auto [ a, b ] = s)
use (a, b);
}
In the preceding example, use
will be called when a
and b
, decomposed from s
, are not equal. The artificial variable used as the decision variable has a unique name and its type here is S
.
Deleting a pointer to an incomplete type
C++26 made it clear that delete
and delete[]
on a pointer to an incomplete class type is invalid. Previously it invoked undefined behavior unless the class had a trivial destructor and no custom deallocator. Consequently, GCC 15 will emit an error in C++26 mode, or a warning in older modes for this example:
struct S;
void foo (S *p)
{
delete p;
}
The Oxford variadic comma
This paper deprecates the use of a variadic ellipsis without a preceding comma in C++26. That means that GCC 15, when compiling the following test case from the proposal, will emit three warnings in C++26 mode:
void d_depr(auto......); // deprecated
void d_okay(auto..., ...); // OK
void g_depr(int...); // deprecated
void g_okay(int, ...); // OK
void h_depr(int x...); // deprecated
void h_okay(int x, ...); // OK
Users can enable the warning in older modes by specifying -Wdeprecated-variadic-comma-omission
.
Remove deprecated array comparison
This paper makes the following program comparing two arrays ill-formed:
int arr1[5];
int arr2[5];
bool same = arr1 == arr2;
The comparison was deprecated in C++20, but GCC 14 emitted a warning only when -Wall
was specified. GCC 15 emits an error in C++26, and a warning in older modes even without -Wall
.
#embed
This paper, first proposed for C23, has made its way into C++26. It gives the programmer a new directive to include binary data into the program.
A dedicated article discusses this feature in detail: How to implement C23 #embed in GCC 15
Defect report resolutions
A number of defect reports were resolved in GCC 15. A few examples follow. The overall status can be viewed here.
Redeclaration of using-declarations
DR 36 clarifies that it’s valid to redeclare an entity via a using-declaration
if the redeclaration happens outside a class scope. As a result, the following program compiles with GCC 15. GCC 14 would report a “redeclaration” error.
enum class E { Smashing, Pumpkins };
void foo() {
using E::Smashing;
using E::Smashing;
using E::Pumpkins;
}
Trivial fixes
GCC 15 fixes DR 1363 and DR 1496. Certain classes that were previously considered trivial are not trivial anymore. The details can be found in this commit message.
Bit-fields and narrowing conversions
The resolution of DR 2627 means that GCC 15 no longer emits the narrowing conversion
warning here:
#include <compare>
struct C {
long long i : 8;
};
void f() {
C x{1}, y{2};
x.i <=> y.i; // OK
}
Overloaded functions and constraints
DR 2918 deals with deduction from an overload set when multiple candidates succeed and have the same type; the most constrained function is selected. The following test case shows this new behavior:
template<bool B> struct X {
static void f(short) requires B; // #1
static void f(short); // #2
};
void test() {
auto x = &X<true>::f; // OK, deduces void(*)(short), selects #1
auto y = &X<false>::f; // OK, deduces void(*)(short), selects #2
}
Additional updates
This section describes other enhancements in GCC 15.
Fix for range-based for loops
This paper fixes a long-standing problem with range-based for loops, which caused much grief in practice. The issue was that the lifetime of the temporaries used in the initializer of a range-based for loops weren’t extended, causing undefined behavior. In C++23, the problem was corrected and now the lifetime of the temporaries is extended to cover the whole loop.
The effect is that the following program will print in C++20:
~T()
loop
But in C++23, it will print:
loop
~T()
It is possible to control this behavior with the -frange-for-ext-temps
and -fno-range-for-ext-temps
options.
struct T {
int arr[1];
~T() { std::printf ("~T()\n"); }
const int *begin () const { return &arr[0]; }
const int *end () const { return &arr[1]; }
};
const T& f(const T& t) { return t; }
T g() { return T{42}; }
int main ()
{
for (auto e : f (g ()))
std::printf ("loop\n");
}
As an aside, this proposal added the fourth case where lifetime extension takes place. DR 2867, also implemented in GCC 15, added the fifth case, which deals with lifetime extension in structured bindings.
Qualified lookup failures diagnosed early
GCC 15 diagnoses certain invalid qualified lookups earlier, while parsing the template. When the scope of a qualified name is the current instantiation, and qualified lookup finds nothing at template definition time, then we know that qualified lookup will find nothing at instantiation time either (unless the current instantiation has dependent bases).
Therefore, such qualified name lookup failures can be diagnosed ahead of time. This is allowed by the C++ standard—[temp.res.general]/6 says: “The validity of a templated entity may be checked prior to any instantiation.” Consequently, the following program is rejected by GCC 15:
template<typename T>
struct S {
void foo() {
int i = this->nothere;
};
};
But this one isn’t:
template<typename T>
struct S : T {
void foo() {
int i = this->nothere;
};
};
because nothere
could be found in the dependent base.
Concepts TS removed
The support for Concepts TS was removed and -fconcepts-ts
has no effect anymore. Programs using the Concepts TS features need to convert their code to C++20 Concepts. For example:
template<typename T>
concept C = true;
C{T} void foo (T); // write template<C T> void foo (T);
-fassume-sane-operators-new-delete
GCC 15 gained the -fassume-sane-operators-new-delete
option which can be used to adjust the behavior regarding optimizations around calls to replaceable global operators new and delete. The manual provides more information about this option.
C++ modules
GCC 15 greatly improved the modules code. For instance, module std
is now supported (even in C++20 mode).
Compile-time speed improvements
Code that uses a lot of template specializations should compile faster with GCC 15 due to the improvements in the hashing of template specializations.
flag_enum
GCC 15 has a new attribute called flag_enum
(see the manual for more information). It can be used to suppress a -Wswitch
warning emitted when enumerators are used in bitwise operations. For example, without the attribute the following test case:
enum [[gnu::flag_enum]] E { cheer = 1, blue = 2 };
void f (enum E e) {
switch (e) {
case blue|cheer:
default:;
}
}
would result in a case value ‘3’ not in enumerated type ‘E’
warning.
New prvalue optimization
GCC 15 tries harder to constant evaluate class prvalues used as function arguments. Previously, there was a discrepancy between the behavior of:
Widget w{"Shepp"};
do_something (w);
and:
do_something (Widget{“Shepp”});
As a result of this optimization, GCC is able to compile code that it previously wasn’t.
C++11 attributes in C++98
GCC 15 allows C++11 attributes to be used even in C++98, which previous versions of GCC sometimes refused to compile. For example, the following code compiles even in C++98 mode:
struct [[gnu::packed]] S { int sardines; };
With -Wpedantic
the compiler warns when it encounters a C++11 attribute in C++98 mode.
New and improved warnings
GCC's set of warning options have been enhanced in GCC 15.
-Wtemplate-body
Errors in uninstantiated templates are IFNDR (ill-formed, no diagnostic required), but GCC progressively diagnoses more and more errors in template definitions ahead of time to catch errors sooner. For example:
template<typename>
void foo ()
{
const int n = 42;
++n; // error, no valid instantiation exists
}
For various reasons, the more aggressive behavior can be undesirable so GCC 15 added the -Wno-template-body
option, which can be used to disable the template errors.
-Wself-move
The -Wself-move warning now warns even in a member-initializer-list
:
#include <utility>
struct B {
int i_;
B(int) : i_(std::move(i_)) { } // warning
};
-Wdefaulted-function-deleted
In GCC 15 we fixed our handling of [dcl.fct.def.default]
. For example, the following test used to be rejected with an error:
struct C {
C(const C&&) = default;
};
But instead, the move constructor should be marked as deleted. The reason is that the function’s type doesn’t match the type of an implicit move constructor. GCC 15 accepts the code, but warns about it by default with this new warning.
-Wheader-guard
This new warning catches typos in header file guarding macros. For instance, including the following .h
file:
#ifndef STDIO_H
#define STDOI_H
extern void elmo ();
extern void hope ();
#endif
will cause a warning to be emitted (if -Wall
was specified): did you mean ‘STDIO_H’?
-Wdangling-reference
This warning has been improved in GCC 15. For instance, it no longer warns for empty classes.
Acknowledgments
As usual, I'd like to thank my coworkers at Red Hat who made the GNU C++ compiler so much better, notably Jason Merrill, Jakub Jelinek, Patrick Palka, and Jonathan Wakely. We would also like to thank Nathaniel Shead for his work on C++ modules.
Last updated: April 25, 2025