Featured image for: Value range propagation in GCC with Project Ranger.

The latest major version of the GNU Compiler Collection (GCC), 13.1, was released in April 2023. Like every major GCC release, this version brings many additions, improvements, bug fixes, and new features. GCC 13 is already the system compiler in Fedora 38Red Hat Enterprise Linux (RHEL) users will get GCC 13 in the Red Hat GCC Toolset (RHEL 8 and RHEL 9). It's also possible to try GCC 13 on godbolt.org and similar web pages.

Like the article I wrote about GCC 10 and GCC 12, this article describes only new features implemented in the C++ front end; it does not discuss developments in the C++ language itself. Interesting changes in the standard C++ library that comes with GCC 13 are described in a separate blog post: New C features in GCC 13

The default dialect in GCC 13 is -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++20 and others. Note that C++20 and C++23 features are still experimental in GCC 13.

C++23 features

This section describes the following new C++23 features:

  • static_assert (false) in templates
  • De-deprecating volatile compound operations
  • Relaxing constexpr restrictions
  • Static operators
  • Extended floating-point types
  • Simpler implicit move
  • Equality operator fix
  • Portable assumptions
  • char8_t compatibility fix
  • Labels at the end of compound statements
  • Traits to detect reference binding to temporary
  • C++ Contracts

static_assert (false) in templates

GCC 13 resolves P2593R0 / CWG 2518. The consequence is that a failing static_assert is only ill-formed at instantiation time. In other words, this program compiles without errors in all C++ modes with GCC 13:

template<typename> void f()
  static_assert (false, "");

because static_assert (false) in uninstantiated templates is now accepted. (GCC 12 rejected the example above.)

De-deprecating volatile compound operations

P2327R1 partially reverts C++20 P1152R4, which deprecated many uses of volatile. As a consequence, bit-wise operations on volatile operands no longer warn:

volatile int vi;
int i;

void g()
  vi ^= i; // no -Wvolatile warning
  vi |= i; // no -Wvolatile warning
  vi &= i; // no -Wvolatile warning

The change was backported to GCC 12 and 11 as well, so those versions also don’t warn for the test case above. A related defect report, CWG 2654, was recently approved, meaning that the rest of the compound assignment operators were un-deprecated as well. GCC 13 already implements this defect report, so the warning doesn’t trigger for other compound operations such as +=.

Relaxing constexpr restrictions

It’s become customary to relax restrictions about the usage of the constexpr keyword since its introduction in C++11. C++23 doesn’t break this habit. In C++23 (but not earlier modes), P2647R1 allows using static constexpr variables in constexpr functions:

constexpr char
test ()
  static constexpr char c[] = "Hello World"; // OK in C++23
  return c[1];

static_assert (test () == 'e');

In a similar vein, P2448R2 brings further constexpr relaxation: in C++23, a constexpr function’s return type or the type of its parameter does not have to be a literal type anymore, and, perhaps more importantly, a constexpr function does not necessarily need to satisfy the requirement of a core constant expression (but actually calling such a function will result in a compile-time error). The intent is to allow functions to be marked constexpr that will later become usable in a constant expression, once other functions that they call become constexpr.

GCC offers a new option, -Winvalid-constexpr, to get a diagnostic when a function could not be invoked in a constexpr context yet even in C++23 mode.

void f (int& i);
constexpr void
g (int& i)
  f(i); // warns by default in C++20, in C++23 only with -Winvalid-constexpr

Static operators

GCC 13 implements both P1169R4 - static operator() and P2589R1 - static operator[]. As the names suggest, these proposals allow the programmer to create a static function call operator and a static subscript operator. Every non-static member function needs to pass the invisible this pointer, which causes additional overhead when such a function is invoked. A static member function avoids the overhead because it doesn’t get the implicit object parameter.

struct S {
  static constexpr bool operator() (int x, int y) { return x < y; }
constexpr S s;
static_assert (s (1, 2));

void g()
  S::operator()(1, 2);  // OK in C++23

Similarly, operator[] can be marked static as well:

struct S {
  S() {}
  static int& operator[]() { return mem[0]; }
  static int mem[64];

void g()
  S s;

Interested readers can read more about the motivation for this change here.

Extended floating-point types

Since GCC 13 implements P1467R9, users can now use types such as std::float16_t and similar:

#include <stdfloat>

int main ()
  std::float16_t f16 = 1.0f16;
  std::float32_t f32 = 2.0f32;
  std::float64_t f64 = 3.0f64;
  std::float128_t f128 = 4.0f128;

These types are becoming popular in fields like machine learning, computer graphics, weather modelers and similar, where it’s typically required to perform a huge amount of computations, but what precision is important depends on the particular use case. std::float32_t and std::float64_t are available on almost every architecture; std::float16_t is currently available on x86_64, aarch64, and a few other architectures; and std::float128_t is available on architectures that support the __float128/_Float128 types.

On x86_64 and aarch64, std::bfloat16 is supported as well (the support comes with software emulation as well):

  std::bfloat16_t x = 1.0bf16;

Simpler implicit move

The rules mandating implicit move unfortunately keep changing; over the years we have had at least P0527R1, P1155R3, and P1825R0. C++23 brought P2266R3, attempting to simplify the rules. For example, the cumbersome maybe-double overload resolution rule was removed. Additionally, P2266 enabled the implicit move even for functions that return references, e.g.:

struct X { };

X&& foo (X&& x) {
  return x;

As a consequence, previously valid code may not compile anymore in C++23:

int& g(int&& x)
  return x; 

Because x is treated as an rvalue in C++23, and it’s not allowed to bind a non-const lvalue reference to an rvalue. For more information, please see the Porting To documentation.

Equality operator fix

As a previous blog explained, the implicit reversing of operator== made some valid C++17 code ill-formed in C++20, for instance when a class defines comparison operators that are accidentally asymmetric:

struct S {
  bool operator==(const S&) { return true; } // mistakenly non-const
  bool operator!=(const S&) { return false; } // mistakenly non-const

bool b = S{} != S{}; // well-formed in C++17, ambiguous in C++20

The problem was that the asymmetric operator== was compared to itself in reverse. GCC implemented a tiebreaker to make the test case above work even in C++20, but the C++ committee resolved the issue in a different way: P2468R2 says that if there is an operator!= with the same parameter types as the operator==, the reversed form of the operator== is ignored. GCC 13 implements the standardized approach.

Portable assumptions

GCC 13 gained support for P1774R8, a paper describing how a programmer can use the construct [[assume(expr)]] to allow the compiler to assume that expr is true and optimize the code accordingly. Most compilers already provide a non-standard way to achieve this. For example, GCC supports the __builtin_unreachable built-in function. When used correctly, the resulting code may be both smaller and faster than a version without the [[assume]].

Consider the following (silly) function:

foo (int x, int y)
  [[assume (x >= y)]];
  if (x == y)
    return 0;
  else if (x > y)
    return 1;
    return -1;

And the difference in the (x86) output assembly without/with the assume attribute:

@@ -8,11 +8,7 @@ _Z3fooii:
xorl    %eax, %eax
cmpl    %esi, %edi
-    je    .L1
-    setg    %al
-    movzbl    %al, %eax
-    leal    -1(%rax,%rax), %eax
+    setne    %al

With the attribute, in this case, all the compiler needs to do is to check if x and y are equal, because it knows it can assume that x cannot be less than y; this results in better output code. Such an optimization is typically the result of the Value Range Propagation optimization taking place.

Note, however, that if the assumption is violated, the code triggers undefined behavior and the compiler is then free to do absolutely anything, so the attribute should be used sparingly and with great care. Also note that the compiler is free to ignore the attribute altogether.

char8_t compatibility fix

P0482R6, which added the char8_t type, didn’t permit

const char arr[] = u8"hi";

But that caused problems in practice, so the example above is allowed under P2513R4, which GCC 13 implements.

Labels at the end of compound statements

GCC 13 implements proposal P2324R2. C2X (the next major C language standard revision) has started allowing labels at the end of a compound statement (which is, for example, before a function’s final }) without a following ;. The C2X proposal was implemented in GCC 11. To minimize differences between C and C++ in this regard, C++ followed suit:

p2324 ()
  int x;
  x = 1;
last: // no error in C++23

Traits to detect reference binding to temporary

GCC 13 supports P2255R2, which adds two new type traits to detect reference binding to a temporary. They can be used to detect code like

   std::pair<const std::string&, int> p("meow", 1);

which is incorrect because it always creates a dangling reference, because the std::string temporary is created inside the selected constructor of std::pair, and not outside it. These traits are called std::reference_constructs_from_temporary and std::reference_converts_from_temporary.

We have made use of these new traits in the standard C++ library to detect buggy code. For example, certain wrong uses of std::function, std::pair, and std::make_from_tuple are now caught and an error is issued.


Even though C++ Contracts are not in the C++ standard yet (although they briefly were in N4820—see Contract Attributes), GCC 13 implements a draft of C++ Contracts. This feature is highly experimental and has to be enabled by the -fcontracts option. (It also requires that the program is linked with -lstdc++exp.) Here’s an example of the pre feature:

void f (int x)
  [[ pre: x >= 0 ]]  // line 3

int main ()
  f (1);  // OK
  f (0);  // OK
  f (-1); // oops

This program, when compiled with -fcontracts -std=c++20 and run, will output the following:

$ ./contracts_demo
contract violation in function f at g.C:3: x >= 0
terminate called without an active exception
Aborted (core dumped)

Defect report resolutions

A number of defect reports were resolved in GCC 13. A few examples follow.

operator[] and default arguments

P2128R6, which added support for the multidimensional subscript operator, meant to allow default arguments, but accidentally did not. This was fixed in CWG 2507, and the following example compiles in C++23 mode:

struct A {
  void operator[](int, int = 42);

attributes on concepts

Since CWG 2428, it’s permitted to have attributes on concepts:

template<typename T>
concept C [[deprecated]] = true;

consteval in default arguments

Spurred by problems revolving around the usage of source_location::current, CWG 2631 clarifies that immediate function calls in default arguments are not evaluated until the default argument is used (rather than being evaluated where they are defined, as part of the semantic constraints checking).

Additional updates

This section describes other enhancements in GCC 13

Concepts fixes

The C++ Concepts code has gotten a lot of bug fixes and a number of loose ends were tied up.

If you had issues with GCC 12 on concepts-heavy code, chances are GCC 13 will do a much better job.

Mixing of GNU and standard attributes

GCC 13 allows mixing GNU and standard (of the [[ ]] form) attributes. Not allowing it caused problems with code, like:

  struct __attribute__ ((may_alias)) alignas (2) struct S { };


   #define EXPORT __attribute__((visibility("default")))
   struct [[nodiscard]] EXPORT F { };

Reduced memory usage and compile time

In GCC 13, we implemented various optimizations that reduce memory usage of the compiler. For example, specialization of nested templated classes has been optimized by reducing the number of unnecessary substitutions. Details can be found here.  Another optimization was to reduce compile time by improving hashing of typenames.

To improve compile times, the compiler in GCC 13 provides new built-ins which the standard C++ library can use. It is generally faster to use a compiler built-in rather than instantiating a (potentially large) number of class templates and similar. For instance, GCC 13 added __is_convertible and __is_nothrow_convertible, as well as __remove_cv, __remove_reference and __remove_cvref built-ins.

Another optimization was to reduce the number of temporaries when initializing an array of std::string.


The C++ front end now understands the new option -nostdlib++, which enables linking without implicitly linking in the C++ standard library.

-fconcepts option cleaned up

Previously, -fconcepts in C++17 meant the same thing as -fconcepts-ts (enabling Concepts Technical Specification which allows constructs not allowed by the standard) in C++20. This oddity was cleaned up and now -fconcepts no longer implies -fconcepts-ts prior to C++20. (We recommend using -std=c++20 if your code uses C++ Concepts.)

New and improved warnings

GCC's set of warning options have been enhanced in GCC 13.

-Wparentheses and operator=

-Wparentheses in GCC 13 warns when an operator= is used as a truth condition:

struct A {
  A& operator=(int);
  operator bool();

f (A a)
  if (a = 0); // warn

Various std::move warnings improved

GCC 12 already had a warning which warns about pessimizing uses of std::move in a return statement. (See a related blog post for more on this.) However, the warning didn’t warn about returning a class prvalues, where a std::move also prevents the Return Value Optimization. This has been fixed, and the warning now warns about the std::move in:

T fn()
  T t;
  return std::move (T{});

as well. Moreover, -Wpessimizing-move warns in more contexts. For example:

   T t = std::move(T());
   T t(std::move(T()));
   T t{std::move(T())};
   T t = {std::move(T())};
   void foo (T);
   foo (std::move(T()));

A related warning, -Wredundant-move, was extended to warn when the user is moving a const object as in:

struct T { };

T f(const T& t)
  return std::move(t);

where the std::move is redundant, because T does not have a T(const T&&) constructor (which is very unlikely). Even with the std::move, T(T&&) would not be used because it would mean losing the const qualifier. Instead, T(const T&) will be called.

New warning: -Wself-move

Relatedly to the previous paragraph, GCC 13 gained a new warning, which warns about useless “self” moves as in the example below.

   int x = 42;
   x = std::move (x);

New warning: -Wdangling-reference

GCC 13 implements a fairly bold new warning to detect bugs in the source code when a reference is bound to a temporary whose lifetime has ended, which is undefined behavior. The canonical example is

 int n = 1;
 const int& r = std::max(n-1, n+1); // r is dangling

where both temporaries (that had been created for n-1 and n+1) were destroyed at the end of the full expression. This warning (enabled by -Wall) detects this problem. It works by employing a heuristic which checks if a reference is initialized by a function call that returns a reference and at least one parameter of the called function is a reference that is bound to a temporary.

Because the compiler does not check the definition of the called function (and often the definition isn’t even visible), the warning can be fooled, although in practice it doesn’t happen very often. However, there are functions like std::use_facet that take and return a reference but don’t return one of its arguments. In such cases, we suggest suppressing the warning by using a #pragma:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
const T& foo (const T&) { ... }
#pragma GCC diagnostic pop

Subsequently, the warning was extended to also warn about a common “footgun” concerning std::minmax:

auto v = std::minmax(1, 2);

which, perhaps not obviously, also contains a dangling reference: the selected std::minmax overload returns std::pair<const int&, const int&> where the two const int references are bound to temporaries. Since its inception, the warning has been tweaked a number of times. For instance, we adjusted it to ignore reference-like classes, because those tended to provoke false positives.

New warning: -Wxor-used-as-pow

This warning warns about suspicious uses of the exclusive OR operator ^. For instance, when the user writes 2^8, it’s likely that they actually meant 1 << 8. To reduce the number of false positives and make the warning useful in practice, it only warns when the first operand is the decimal constant 2 or 10.

New option: -Wchanges-meaning

In C++, a name in a class must have the same meaning in the complete scope of the class.  To that effect, GCC 12 emits an -fpermissive error for

struct A {};
struct B { A a; struct A { }; }; // error, A changes meaning

In GCC 13, it is possible to disable this particular diagnostic by using the new command-line option -Wchanges-meaning. Having a dedicated option to control this diagnostic is useful because other compilers aren’t as consistent in detecting this invalid code.

Color function names in diagnostic

This change can be best demonstrated with a screenshot (Figure 1).

Function names appear in color in GCC 13.
Figure 1: C function names formatted in color.


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.

Last updated: August 14, 2023