GCC 15 的新增 C++ 功能
New C++ features in GCC 15

原始链接: https://developers.redhat.com/articles/2025/04/24/new-c-features-gcc-15

GCC 15预计将于2025年4月/5月发布,带来了许多C++前端增强功能。虽然C++20/23/26特性仍在实验阶段,但GCC 15已经实现了几个C++26特性,例如包索引、结构化绑定的属性以及带原因消息的`=delete`。可变参数友元和constexpr placement new也得到了支持。 结构化绑定现在允许在条件语句中使用。GCC 15加强了对删除指向不完整类型的指针的限制,并警告了Oxford可变参数逗号和已弃用的数组比较。用于包含二进制数据的`#embed`指令也可用。 几个缺陷报告已被解决,提高了标准的遵从性。增强功能包括基于范围的for循环的生命周期扩展修复以及更早地诊断限定查找失败。Concepts TS已被移除,替换为C++20 Concepts。模块支持得到了极大的改进。GCC 15还包含新的警告选项,例如`-Wtemplate-body`、`-Wself-move`、`-Wdefaulted-function-deleted`和`-Wheader-guard`,以增强代码分析。

Hacker News 的一个帖子讨论了 GCC 15 中新的 C++ 特性。一个关键点是 GCC 改进了对 C++20 中基于范围的 for 循环的处理,这可能会延长临时对象的生存期。虽然看似有帮助,但这项修复可能会导致可移植性问题,因为在 GCC 15 中工作的代码可能会在其他 C++20 编译器上由于不同的生命周期管理而中断。评论者对持续存在的编译器可移植性问题表示遗憾,尽管编译器生态系统正在缩小。GCC 新的模块支持(与 clang 和 MSVC 保持一致)受到了好评。`#embed` 特性也因简化安装程序创建、降低开发者门槛而受到关注,尽管 Windows 注册表管理仍然需要手动处理。对基于范围的 for 循环中未定义行为 (UB) 的修复也受到了好评。

原文

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.

联系我们 contact @ memedata.com