The GNU Compiler Collection (GCC) 10.1 was released in May 2020. Like every other GCC release, this version brought many additions, improvements, bug fixes, and new features. Fedora 32 already ships GCC 10 as the system compiler, but it's also possible to try GCC 10 on other platforms (see godbolt.org, for example). Red Hat Enterprise Linux (RHEL) users will get GCC 10 in the Red Hat Developer Toolset (RHEL 7), or the Red Hat GCC Toolset (RHEL 8).
This article focuses on the part of the GCC compiler on which I spend most of my time: The C++ front end. My goal is to present new features that might be of interest to C++ application programmers. Note that I do not discuss developments in the C++ language itself, although some language updates overlap with compiler updates. I also do not discuss changes in the standard C++ library that comes with GCC 10.
We implemented many C++20 proposals in GCC 10. For the sake of brevity, I won't describe them in great detail. The default dialect in GCC 10 is -std=gnu++14
; to enable C++20 features, use the -std=c++20
or -std=gnu++20
command-line option. (Note that the latter option allows GNU extensions.)
C++ concepts
While previous versions of GCC (GCC 6 was the first) had initial implementations of C++ concepts, GCC 10 updated concepts to conform to the C++20 specification. This update also improved compile times. Subsequent patches have improved concepts-related diagnostics.
In C++ template parameters, typename
means any type. But most templates must be constrained in some way; as an example, you want to only accept types that have certain properties, not just any type. Failing to use the correct type often results in awful and verbose error messages. In C++20, you can constrain a type by using a concept, which is a compile-time predicate that specifies the set of operations that can be applied to the type. Using GCC 10, you can define your own concept (or use one defined in <concepts>
):
#include <type_traits> // Require that T be an integral type. template<typename T> concept C = std::is_integral_v<T>;
And then use it like this:
template<C T> void f(T) { } void g () { f (1); // OK f (1.2); // error: use of function with unsatisfied constraints }
Starting with GCC 10, the C++ compiler also supports constrained auto. Using our concept above, you can now write:
int fn1 (); double fn2 (); void h () { C auto x1 = fn1 (); // OK C auto x2 = fn2 (); // error: deduced initializer does not satisfy placeholder constraints }
Coroutines
GCC 10 supports stackless functions that can be suspended and resumed later without losing their state. This feature lets us execute sequential code asynchronously. It requires the -fcoroutines
command-line option.
Unevaluated inline-assembly in constexpr
functions
Code like this now compiles:
constexpr int foo (int a, int b) { if (std::is_constant_evaluated ()) return a + b; // Not in a constexpr context. asm ("/* assembly */"); return a; }
See the proposal for details.
Comma expression in array subscript expressions
This type of expression is now deprecated, so GCC 10 warns for code like this:
int f (int arr[], int a, int b) { return arr[a, b]; }
Only a top-level comma is deprecated, however, so arr[(a, b)]
compiles without a warning.
Structured bindings
GCC 10 improves and extends structured bindings. For instance, it's now possible to mark them static
:
struct S { int a, b, c; } s; static auto [ x, y, z ] = s;
This example doesn't compile with GCC 9, but it compiles with GCC 10.
The constinit
keyword
GCC 10 uses the C++20 specifier constinit
to ensure that a (static storage duration) variable is initialized by a constant initializer.
This might alleviate problems with the static initialization order fiasco. However, the variable is not constant: It is possible to modify it after initialization has taken place. Consider:
constexpr int fn1 () { return 42; } int fn2 () { return -1; } constinit int i1 = fn1 (); // OK constinit int i2 = fn2 (); // error: constinit variable does not have a constant initializer
GCC doesn't support Clang's require_constant_initialization
attribute, so you can use __constinit
in earlier C++ modes, as an extension, to get a similar effect.
Deprecated uses of volatile
Expressions that involve both loads and stores of a volatile
lvalue, such as ++
or +=
, are deprecated. The volatile
-qualified parameter and return types are also deprecated, so GCC 10 will warn in:
void fn () { volatile int v = 42; // Load + store or just a load? ++v; // warning: deprecated }
Conversions to arrays of unknown bound
Converting to an array of unknown bound is now permitted, so the following code compiles:
void f(int(&)[]); int arr[1]; void g() { f(arr); } int(&r)[] = arr;
Note: Conversion in the other direction—from arrays of unknown bound—currently is not allowed by the C++ standard.
constexpr new
This feature allows dynamic memory allocation at compile time in a constexpr
context:
constexpr auto fn () { int *p = new int{10}; // ... use p ... delete p; return 0; } int main () { constexpr auto i = fn (); }
Note that the storage allocated at compile time in a constexpr
context must also be freed at compile time. And, given that constexpr
doesn't allow undefined behavior, use-after-free
is a compile-time error. The new
expression also can't throw. This feature paves the way for constexpr
standard containers such as <vector>
and <string>
.
The [[nodiscard]]
attribute
The [[nodiscard]]
attribute now supports an optional argument, like so:
[[nodiscard("unsafe")]] int *fn ();
See the proposal for details.
CTAD extensions
C++20 class template argument deduction (CTAD) now works for alias templates and aggregates, too:
template <typename T> struct Aggr { T x; }; Aggr a = { 1 }; // works in GCC 10 template <typename T> struct NonAggr { NonAggr(); T x; }; NonAggr n = { 1 }; // error: deduction fails
Parenthesized initialization of aggregates
You can now initialize an aggregate using a parenthesized list of values such as (1, 2, 3)
. The behavior is similar to {1, 2, 3}
, but in parenthesized initialization, the following exceptions apply:
- Narrowing conversions are permitted.
- Designators (things like
.a = 10
) are not permitted. - A temporary object bound to a reference does not have its lifetime extended.
- There is no brace elision.
Here's an example:
struct A { int a, b; }; A a1{1, 2}; A a2(1, 2); // OK in GCC 10 -std=c++20 auto a3 = new A(1, 2); // OK in GCC 10 -std=c++20
Trivial default initialization in constexpr
contexts
This usage is now allowed in C++20. As a result, a constexpr
constructor doesn't necessarily have to initialize all the fields (but reading an uninitialized object is, of course, still forbidden):
struct S { int i; int u; constexpr S() : i{1} { } }; constexpr S s; // error: refers to an incompletely initialized variable S s2; // OK constexpr int fn (int n) { int a; a = 5; return a + n; }
constexpr dynamic_cast
In constexpr
contexts, you can now evaluate a dynamic_cast
at compile time. Virtual function calls in constant expressions were already permitted, so this proposal made it valid to use a constexpr
dynamic_cast
, like this:
struct B { virtual void baz () {} }; struct D : B { }; constexpr D d; constexpr B *b = const_cast<D*>(&d); static_assert(dynamic_cast<D*>(b) == &d);
Previously, a constexpr
dynamic_cast
would have required a runtime call to a function defined by the C++ runtime library. Similarly, a polymorphic typeid
is now also allowed in constexpr
contexts.
Note: C++20 modules are not supported in GCC 10; they are still a work in progress (here is a related proposal). We hope to include them in GCC 11.
Additional updates
In non-C++20 news, the C++ compiler now detects modifying constant objects in constexpr
evaluation, which is undefined behavior:
constexpr int fn () { const int i = 5; const_cast<int &>(i) = 10; // error: modifying a const object return i; } constexpr int i = fn ();
GCC also handles the case when a constant object under construction is being modified and doesn't emit an error in that case.
Narrowing conversions
Narrowing conversions are invalid in certain contexts, such as list initialization:
int i{1.2};
GCC 10 is able to detect narrowing in more contexts where it's invalid, for instance, case values in a switch
statement:
void g(int i) { switch (i) case __INT_MAX__ + 1u:; }
The noexcept
specifier
GCC 10 properly treats the noexcept
specifier as a complete-class context. As with member function bodies, default arguments, and non-static data member initializers, you can declare names used in a member function's noexcept
specifier later in the class body. The following valid C++ code could not be compiled with GCC 9, but it compiles with GCC 10:
struct S { void foo() noexcept(b); static constexpr auto b = true; };
The deprecated
attribute
You can now use the deprecated
attribute on namespaces:
namespace v0 [[deprecated("oh no")]] { int fn (); } void g () { int x = v0::fn (); // warning: v0 is deprecated }
GCC 9 compiles this code but ignores the attribute, whereas GCC 10 correctly warns about using entities from the deprecated namespace.
Defect report resolutions
We resolved several defect reports (DRs) in GCC 10. One example is DR 1710, which says that when we are naming a type, the template
keyword is optional. Therefore, the following test compiles without errors with GCC 10:
template<typename T> struct S { void fn(typename T::template B<int>::template C<int>); void fn2(typename T::B<int>::template C<int>); void fn3(typename T::template B<int>::C<int>); void fn4(typename T::B<int>::C<int>); };
Another interesting DR is DR 1307, which clarified how overload resolution ought to behave when it comes to choosing a better candidate based on the size-of-array initializer list. Consider the following test:
void f(int const(&)[2]); void f(int const(&)[3]) = delete; void g() { f({1, 2}); }
GCC 9 rejects this test because it can't decide which candidate is better. Yet it seems obvious that the first candidate is best (never mind the = delete
part; deleted functions participate in overload resolution). GCC 10 chooses this option, which lets the code compile.
Note: You can find the overall defect resolution status on the C++ Defect Report Support in GCC page.
Conclusion
In GCC 11, we plan to finish up the remaining C++20 features. For progress so far, see the C++2a Language Features table on the C++ Standards Support in GCC page. GCC 11 will also switch the default dialect to C++17 (it has already happened). Please do not hesitate to file bugs in the meantime, and help us make GCC even better!
Acknowledgments
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: February 5, 2024