The next major version of the GNU Compiler Collection (GCC), 14.1, was released on May 7 2024. Like every major GCC release, this version brings many additions, improvements, bug fixes, and new features. GCC 14 is already the system compiler in Fedora 40. Red Hat Enterprise Linux (RHEL) users will get GCC 14 in the Red Hat GCC Toolset (RHEL 8 and RHEL 9), and as the system compiler in RHEL 10. It's also possible to try GCC 14 on Compiler Explorer and similar pages.
Like the article I wrote about GCC 13, this article describes only new features implemented in the C++ front end; it does not discuss developments in the C++ language itself which will have a separate blog post. Interesting changes in the standard C++ library that comes with GCC 14 will be described in a separate blog post as well.
The default dialect in GCC 14 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. Note that C++20, C++23, and C++26 features are still experimental in GCC 14.
C++26 features
Trivial infinite loops and UB
This proposal brings C and C++ a little closer together. The C++ standard, unlike the C standard, defined forward progress in such a way that it assumed that even trivial infinite loops will eventually terminate; if they didn’t, the program invoked undefined behavior. This is a problem if you write very low-level code where such infinite loops are common. The proposal added a definition of a trivially empty iteration statement, whereby a loop whose controlling expression is a constant expression that evaluates to true is a trivial infinite loop; these are now included in the definition of forward progress. For example, in GCC 14, the following snippet no longer causes undefined behavior (in all C++ dialects, not only C++26):
void
g ()
{
while (true)
/* do something */;
}
Static storage for braced initializers
This proposal, implemented in GCC 14, allows the compiler to better optimize code using std::initializer_list
. The implementation of std::initializer_list
uses a backing array for its data. Prior to this proposal, the standard didn’t allow the compiler to allocate the backing array in static storage even when the contents were constant data because all backing arrays had to be distinct. So the compiler had no other choice than to copy the data from read-only memory onto the stack every time the list was used. The requirement is no longer mandated by the standard, therefore the backing arrays can be shared (which was already the case for string-literals). In short, GCC 14 optimizes away the memcpy
calls copying the backing array in the following snippet.
#include <initializer_list>
void f(std::initializer_list<int> il);
void g() {
f({3,2,1, 0, 1, 2, 3}); // array in .rodata
}
Unevaluated strings
GCC 14, in C++26 mode, implements P2361R6 which adds the notion of unevaluated strings. Unevaluated strings appear in context such as _Pragma
, static_assert
, or the [[nodiscard]]
and [[deprecated]]
attributes. These strings are not used in the program at runtime, so they are not converted to the execution encoding like evaluated strings. Since they aren’t going to be converted to a different string encoding, they cannot contain numeric escape sequences, and they cannot have any prefixes. In practice, this means that the following two lines will cause diagnostics to be emitted in C++26 mode:
static_assert (true, u"foo"); // error in C++26
static_assert (true, "\x{20}"); // warning in C++26 in pedantic mode
Constexpr cast from void*
GCC 14 implements P2738R1, so the following test is accepted in C++26. This feature allows users to perform various type erasure tricks in constexpr code. (In C++23 and earlier, the compiler still emits an error about trying to cast from void*
in a constant expression.)
struct A { int i; };
struct B { A a; };
constexpr int f()
{
B b { 42 };
void *p = &b;
A* ap = static_cast<A*>(p); // OK
return ap->i;
}
static_assert (f() == 42);
User-generated static_assert messages
With this proposal implemented in GCC 14, the user is allowed to use static_assert
more generally as it now also accepts user-generated messages (which are manifestly constant evaluated). For example:
struct error {
constexpr int size () const { return sizeof "hello" - 1; }
constexpr const char *data () const { return "hello"; }
};
static_assert (false, error{});
Gives the following error message:
error: static assertion failed: hello
Placeholder variables
GCC 14 implements proposal P2169R4. This proposal allows users to use the special _
variable, which serves as a placeholder for an unnamed variable which will not produce the “unused variable” diagnostic even when it’s not used. _
can also redefine an existing declaration in the current scope (but actually using _
would trigger an error if more than one variable has been declared with that name):
X foo ();
void
g ()
{
auto _ = foo ();
auto _ = 42; // OK
int i = _; // error: reference to ‘_’ is ambiguous
}
Returning reference to temporary
P2748R5 makes it ill-formed to return a reference to temporary. GCC has warned about such dangerous code for a long time, but C++26 makes it an error.
auto&&
foo ()
{
return 42; // ill-formed in C++26
}
C++23 features
Deducing this
GCC 14 implemented this proposal, which a lot of users clamored for. It allows users to add an explicit this
parameter to non-static member functions; this is called an explicit object parameter. That way, you can deduce the type and value category of the class object. This is useful to avoid code duplication; previously, the developer was forced to write several member function overloads: non-const
, const
, and with ref-qualifiers even const &
and &&
(this ignores volatile
and similar frills). With this proposal, instead of:
class S {
int value;
public:
int get_add (int i) & { return value + i; }
int get_add (int i) && { return value + i; }
int get_add (int i) const & { return value + i; }
};
auto g (S s, const S& sr) -> int
{
auto i1 = s.get_add (42);
auto i2 = sr.get_add (42);
auto i3 = S{}.get_add (42);
return i1 + i2 + i3;
}
You can simplify the code to:
class S {
int value;
public:
template<typename Self>
int get_add (this Self&& self, int i) { return self.value + i; }
};
S s{};
s.get_add(1); // Self is deduced as S&
std::move(s).get_add(2); // Self is deduced as S
std::as_const(s).get_add(3); // Self is deduced as const S&
Deducing this also allows you to write recursive lambdas. For example:
int
main ()
{
auto fib = [](this auto self, int n) {
if (n < 2)
return n;
return self (n - 1) + self (n - 2);
};
static_assert (fib (6) == 8);
}
There are many more examples where the explicit object parameter can be useful. Naturally, it has some restrictions; for example, an explicit object member function cannot be static or virtual.
References in constant expressions
A modern C++ way to implement a function to get the size of an array can be:
template<typename T, size_t N>
constexpr auto array_size (T (&)[N]) -> size_t {
return N;
}
This function can be used as in this code example:
void g ()
{
int arr[] = { 1, 3, 3, 7 };
constexpr auto sz = array_size (arr);
}
The code works without any surprises. When the array in question was a parameter, however, as in this example:
void g (const int (&arr)[3])
{
constexpr auto sz = array_size (arr);
}
The compiler, due to some fairly arcane rules, rejected the code with error: ‘arr’ is not a constant expression
. The rules have been adjusted and the code works as expected with GCC 14 (in all C++ dialects).
consteval needs to propagate up
Implementing this proposal in GCC 14 turned out to be somewhat knotty. It causes certain functions to be promoted to consteval
, that is, making them immediate functions. Consider:
consteval int id(int i) { return i; }
template <typename T>
constexpr int f(T t)
{
return t + id(t); // id causes f<int> to be promoted to consteval
}
void g(int i)
{
f (3);
}
Previously the code was invalid and therefore the compiler gave an error saying that t
in f
is not a valid constant expression. With this proposal, f
gets promoted to consteval
, which means that the call id(t)
is in an immediate context. So the call does not need to be a constant expression. (This is how consteval
function composition works – one consteval
function calling another one.) Note that making f<int>
consteval
means that the f(3)
call must be a valid constant expression.
The standard describes which expressions cause their enclosing function to be promoted to consteval
; these expressions are called immediate-escalating. For example, a call to an immediate (that is, consteval
) function that is not a constant expression and is not a subexpression of an immediate invocation is an immediate-escalating expression. Not every function can be promoted to consteval
. The standard says that only immediate-escalation functions can be promoted. An example of an immediate-escalating function is a template function declared as constexpr
.
The behavior described in the proposal is enabled by default in C++20 mode or later, but you can suppress function escalation with the -fno-immediate-escalation
command-line option.
CTAD from inherited constructors
GCC 14 supports P2582R1, a proposal which extends CTAD (Class Template Argument Deduction) for inherited constructors. Consequently, the following testcase compiles in C++23 mode:
template <typename T> struct B {
B(T);
};
template <typename T> struct C : B<T*> {
using B<T*>::B;
};
int* p;
C c(p); // OK, deduces C<int>
Defect report resolutions
GCC 14 implements a number of defect report resolutions, which are usually applied for all affected -std
dialects, not only the newest standards. Please see our C++ DR table for a more detailed status.
stricter constinit
CWG issue 2543 clarifies that constinit
variables with non-constant initializers should be diagnosed, even if the variable could be initialized statically.
goto and trivially-initialized objects
Since CWG Issue 2256, goto
can cross the initialization of a trivially initialized object with a non-trivial destructor:
struct A {
~A() { }
};
void f()
{
goto L;
A a;
L:
return;
}
List-initialization and conversions in overload resolution
CWG 1228 reaffirmed the difference between copy-initialization and copy-list-initialization with regard to explicit
candidates: in the context of copy-initialization, only converting (non-explicit
) candidates are considered; in the context of copy-list-initialization, explicit
candidates are considered, but it is an error if an explicit
candidate is actually selected. We implemented this change as part of the PR109159 fix, but it caused issues in practice. The problem was mitigated by CWG 2735 which GCC 14 and GCC 13 implement. Consequently, the test from CWG 2735 compiles again:
struct Z {};
struct X {
explicit X(); // #1
explicit X(const Z &z); // #2
};
struct Y {
Y() : x({}) {} // OK, calls #2, not ambiguous with the copy constructor of X using a temporary initialized by #1
X x;
};
DR 2237 improved
DR 2237 prohibited using a template-id as a constructor or destructor in C++20. GCC 11 implemented this change, but it was too draconic: it issued a hard error and the unclear message hardly brought any joy. GCC 14 downgrades the error to a new -Wtemplate-id-cdtor
warning, and better explains what the problem is.
template<typename T>
struct X {
X<T>(); // template-id not allowed for constructor
~X<T>(); // template-id not allowed for destructor
};
Additional updates
More checking in templates
In GCC 14, non-dependent simple assignments are checked even in templates; that is, some invalid code is checked even when the template has not been instantiated. For example:
int n;
template <int>
void g()
{
-n = 0; // error
}
In-class variable template partial specializations
In-class variable template partial specializations are now accepted by the compiler; previously, these were wrongly rejected:
struct A {
template<class T> static const int var = 0;
template<class T> static const int var<T*> = 1;
template<class T> static const int var<const T*> = 2;
};
static_assert(A::var<int> == 0);
static_assert(A::var<int*> == 1);
static_assert(A::var<const int*> == 2);
constexpr lifetime tracking
GCC 14 implements constexpr lifetime tracking, which allows the compiler to detect using objects after their lifetime has ended while evaluating code at compile time.
struct S {
int x = 0;
constexpr const int& get() const { return x; }
};
constexpr const int& test() {
auto local = S{};
return local.get();
}
constexpr int x = test(); // error: accessing local outside its lifetime
NRVO extended
In GCC 14, the named return value optimization (NRVO) is performed even for variables declared in an inner block of a function:
struct A {
int i;
A(int i) : i(i) { }
A(const A &); // not defined
};
A h() {
{
A a(0);
return a;
}
}
int main () {
A c = h(); // A::A(const A &) not called
}
hot/cold attribute on types
The hot
and cold
attributes can now be applied to classes as well. The effect of the attribute will be propagated to the class’s members:
struct __attribute__((cold)) A {
void feet () { } // feet is cold
};
You can visit the manual for more information about this attribute.
New and improved warnings
-Wnrvo added
Relatedly to the NRVO extension described above, GCC has a new warning that warns when the compiler didn’t elide the copy from a local variable to the return value of a function in a context where it is allowed by the standard (see [class.copy.elision] in the C++ standard for details). For instance, the compiler warns here:
struct A {
A() {}
A(const A&);
};
A test() {
A a, b;
if (true)
return a;
else
return b; // warning: not eliding copy on return in 'A test()'
}
This is because it can’t elide from both a
and b
.
-Wdangling-reference false positives reduced
GCC 13 introduced the -Wdangling-reference
warning, which may detect bugs in the code when a reference to a destroyed object is used. Unfortunately, it had a lot of false positives, raising the users’ ire. Most of the shortcomings were fixed in GCC 14; for example, the warning no longer triggers on std::span
-like classes. We consider a non-union class that has a pointer data member and a trivial destructor std::span
-like. Moreover, if the heuristic still falls short, the users can now use the [[gnu::no_dangling]]
attribute to suppress the warning. See -Wdangling-reference documentation for more information.
New option to show considered candidates
GCC 14 added the -fdiagnostics-all-candidates
option which tells the compiler to show all function candidates it considered when overload resolution failure occurs.
Improved diagnostic for explicit conversion functions
When a conversion function could not be chosen only because it was marked explicit, the compiler now provides a better explanation, rather than just saying that the conversion was invalid:
struct S {
explicit S(int) { }
explicit operator bool() const { return true; } // note: explicit conversion function was not considered
};
void
g ()
{
bool b = S{1}; // error: cannot convert ‘S’ to ‘bool’ in initialization
}
Improved "not a constant expression" diagnostic
When taking the address of a non-static constexpr
variable, the compiler now better explains why that’s a problem. For instance, here:
void test ()
{
constexpr int i = 42;
constexpr const int *p = &i;
}
The compiler says “address of non-static constexpr variable ‘i’ may differ on each invocation of the enclosing function; add ‘static’ to give it a constant address
”.
Modules
C++ modules remain experimental in GCC 14, but dozens of bugs have been fixed in this release.
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. Also thanks to Nathaniel Shead, who has tackled many pesky C++ modules bugs, and waffl3x, who worked on the Deducing This proposal.