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 38. Red 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; 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:
int foo (int x, int y) { [[assume (x >= y)]]; if (x == y) return 0; else if (x > y) return 1; else return -1; }
And the difference in the (x86) output assembly without/with the assume
attribute:
@@ -8,11 +8,7 @@ _Z3fooii: .cfi_startproc xorl %eax, %eax cmpl %esi, %edi - je .L1 - setg %al - movzbl %al, %eax - leal -1(%rax,%rax), %eax -.L1: + setne %al ret .cfi_endproc .LFE0:
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:
void p2324 () { first: int x; second: 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.
Contracts
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 { };
or:
#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
.
-nostdlib++
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(); }; void 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).
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.
Last updated: April 4, 2024