Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Porting your code to C++17 with GCC 11

August 6, 2021
Marek Polacek
Related topics:
C, C#, C++Linux
Related products:
Red Hat Enterprise Linux

Share:

    The GNU Compiler Collection (GCC), which is the standard compiler on GNU/Linux distributions such as Fedora and Red Hat Enterprise Linux, moved from version 14 to version 17 of C++ in April 2021. Thus, the -std=gnu++17 command-line option is now used by default.

    C++17 brings a host of new features, but also deprecates, removes, or changes the semantics of certain constructs. This article looks at some of the issues you might face when switching to GCC 11. Remember that it is always possible to use the previous version of C++ by specifying the -std=gnu++14 option. Moreover, this article deals only with the core language; we won't discuss deprecated or removed features in the standard C++ library (such as auto_ptr). For a broader overview, I encourage visiting the paper Changes between C++14 and C++17. For more information regarding switching to using GCC 11, please see our upstream document, Porting to GCC 11.

    Removed in C++17

    We'll start with what has been removed in C++17: Trigraphs, the register keyword, and increments on the bool type.

    Trigraphs

    In C++, a trigraph is a sequence of three characters starting with ?? that can express single punctuation characters. For instance, \ can be written as ??/. The reason for this is historical: C and C++ use special characters such as [ and ] that are not defined in the ISO 646 character set (and so some keyboards are missing these keys). The positions of these characters in the ISO table might be occupied by different characters in national ISO 646 characters sets, such as ¥ in place of \.

    Trigraphs were meant to allow programmers to enter the characters that weren't on the keyboard. But in practice, trigraphs are likely to be used only by accident, so they were removed in C++17. The removal allows you to play "cute" games like the following test. Can you see why it works?

    bool cxx_with_trigraphs_p () {
      // Are we compiling in C++17??/
      return false;
      return true;
    }

    If for some reason you still need to use trigraphs in C++17 (indeed, there are code bases that still use trigraphs), GCC offers the -trigraphs command-line option.

    The register keyword

    The register keyword was deprecated in C++11 with CWG 809 because it had no practical effect. C++17 cleaned this up further by removing the keyword completely (though it remains reserved for future use). Therefore, code such as the following causes GCC 11 to warn by default, and to issue an error when the -pedantic-errors option is used:

    void f () {
      register int i = 42; // warning: ISO C++17 does not allow 'register' storage class specifier
      int register; // error
    }

    In C++14, you can instruct the compiler to warn using -Wregister, which ought to help you migrate code to C++17. Note that GCC still accepts the GNU explicit register variables extension without warning:

    register int g asm ("ebx");

    Increments on the bool type

    In C++, the -- operator has never been supported for objects of type bool. But ++ on a bool was originally valid, and was deprecated but not removed in C++98. In the spirit of C++17, post-increment and pre-increment are now forbidden, so GCC will give an error for code that uses them:

    template<typename T>
    void f() {
      bool b = false;
      b++; // error: use of an operand of type 'bool' in 'operator++' is forbidden in C++17
      T t{};
      t++; // error: use of an operand of type 'bool' in 'operator++' is forbidden in C++17
    }
    
    void g() {
      f<bool>();
    }

    Instead of ++, simply use b = true or b |= true depending on the specific case.

    Exception specification changes

    Keywords related to exceptions have been added to C++ over various versions of the language. C++17 introduces changes to the noexcept specification while removing dynamic exception specifications.

    Exception specifications are now part of the type system

    C++1 1 added the noexcept exception specification in N3050. Since C++17, noexcept has become part of the type. Thus, the following two functions have the same type in C++14, but different types in C++17:

    int foo () noexcept;
    int bar ();

    However, this doesn't mean that noexcept is part of a function's signature. Therefore, you cannot overload a function as follows:

    int baz();
    int baz() noexcept; // error: different exception specifier (even in C++11)

    Another consequence is that in C++17, a pointer to a function that could potentially throw cannot be converted to a function that cannot throw:

    void (*p)();
    void (*q)() noexcept = p; // OK in C++14, error in C++17

    This change also affects template arguments. The following program will not compile in C++17, because the compiler deduces two conflicting types for the template parameter T:

    void g1 () noexcept;
    void g2 ();
    template<typename T> int foo (T, T);
    void f() {
      foo (g1, g2); // error: void (*)() noexcept vs void (*)()
    }

    Interestingly, this change was first discussed more than 20 years ago in CWG 92, and was finally adopted in P0012R1.

    Removal of dynamic exception specifications

    C++11 deprecated dynamic exception specifications in N3051, and C++17 removed them altogether in P0003R5. The exception is that throw() continues to work in C++17, and is equivalent to noexcept(true), although it has been removed in C++20. Moreover, in C++17, the function std::unexpected was removed. This function used to be called if a function decorated with throw() actually did throw an exception. In C++17, std::terminate is called instead.

    C++17 code cannot use dynamic exception specifications and should replace throw() with noexcept.

    The following example might help to clarify the usage:

    void fn1() throw(int); // error in C++17, warning in C++14
    void fn2() noexcept; // OK since C++11
    void fn3() throw(); // deprecated but no warning in C++17
    void fn4() throw() { throw; }
    // In C++14, calls std::unexpected which calls std::unexpected_handler
    // (which is std::terminate by default).
    // In C++17, calls std::terminate directly.

    New template template-parameter matching

    The C++17 proposal P0522R0: Matching of template template-arguments excludes compatible templates, which fixed DR 150, was implemented in GCC 7 in the -fnew-ttp-matching option but was turned off by default. Because GCC 11 defaults to C++17, the new behavior is enabled by default.

    In the old behavior, the template template-argument for a template template-parameter must be a template with a parameter list that exactly matches the corresponding parameters in the template parameter's parameter list. The new behavior is less strict, by considering default template arguments of template template-arguments in the match.

    This change was problematic for some templates in the standard library. For instance, std::deque is a template with the following default template argument:

    template<typename T, typename Allocator = std::allocator<T>>
    class deque;

    Therefore, the following code works only with the new GCC 11 behavior:

    #include <deque>
    
    template <template <typename> class>
    void fn() {}
    template void fn<std::deque>();

    The workaround is to adjust the declaration so that the parameter expects two type parameters:

    #include <deque>
    
    template <template <typename, typename> class>
    void fn() {}
    template void fn<std::deque>();

    However, the new behavior can also cause code that worked in the old behavior to stop compiling with an error:

    template <int N, int M = N> class A { };
    template <int N, int M> void fn(A<N, M>) {}
    template <int N, template <int> typename T> void fn(T<N>);
    
    void g ()
    {
      A<3> a;
      fn (a); // ambiguous in C++17
    }

    The reason is that A is considered a valid argument for T in the new behavior. Therefore, both function templates are valid candidates, and because neither is more specialized than the other, the function call to fn has become ambiguous.

    It's possible to revert to the old behavior, even in C++17 mode, by using -fno-new-ttp-matching.

    Static constexpr and consteval class members implicitly inline

    The C++17 proposal to introduce inline variables (P0386R2) brought the following change into the [dcl.constexpr] section of the specification: "A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable."

    As a consequence, the member variable A::n in the following example is a definition in C++17. The declaration labeled #2 in a comment was required in C++14. In C++17, the #2 declaration can be removed:

    struct A {
      static constexpr int n = 5; // #1, definition in C++17, declaration in C++14
    };
    
    constexpr int A::n; // #2, definition in C++14, deprecated redeclaration in C++17
    
    auto g()
    {
      return &A::n; // ODR-use of A::n -- needs a definition
    }

    Changes to evaluation order

    C++17 P0145R3 clarified the order of evaluation of various expressions. As the proposal states, the following expressions are evaluated in such a way that a is evaluated before b:

    • a.b
    • a->b
    • a->*b
    • a (b1, b2, b3)
    • b op= a
    • a[b]
    • a << b
    • a >> b

    These rules have caused some changes in the GCC compiler, and certain code might behave differently in C++14 and C++17, as the following test illustrates. It is possible to selectively adjust the compiler behavior using the -fstrong-eval-order={all|some|none} compile-time option, where all is the default in C++17 and some is the default in C++14.

    int fn ()
    {
      int ar[4]{ };
      int i = 0;
      ar[i++] = i;
      return ar[0]; // returns 0 in C++17, 1 in C++14
    }
    
    int fn2 ()
    {
      int x = 2;
      return x << (x = 1, 2); // returns 8 in C++17, 4 in C++14
    }
    
    int fn3 ()
    {
      int x = 6;
      return x >> (x = 5, 1); // returns 3 in C++17, 2 in C++14
    }

    The aforementioned P0145R3 paper also defines the evaluation order when some of the affected operands are overloaded and are using the operator syntax: They follow the order prescribed for the built-in operator.

    Guaranteed copy elision

    C++17 requires guaranteed copy elision, meaning that a copy or move constructor call will be elided completely under certain circumstances (such as when the type of the initializer and target are the same), even when the call has side effects. That means that, theoretically, if something relied on a constructor being instantiated, for example, via copying a function parameter, the program could now fail, because the constructor might not be instantiated in C++17.

    GCC already performed copy/move elision as an optimization even in C++14 mode, so such failures are unlikely to happen in practice. However, the difference is that in C++17 the compiler will not perform access checking on the elided constructor, so code that didn't compile previously could compile now, as demonstrated by the following snippet:

    class A {
      int i;
    public:
      A() : i{42} {}
    private:
      A(const A &);
    };
    
    struct B {
      A a;
      B() : a(A()) {} // OK in C++17, error in C++14
    };

    Summary

    I hope these notes will be useful for developers migrating to GCC 11. As with every major release, we've added new warnings that might turn up when switching compilers. Should you find a bug in these new warnings, please don't hesitate to open a new problem report as outlined in the GCC bugs page.

    Last updated: October 8, 2024

    Related Posts

    • Detecting memory management bugs with GCC 11, Part 1: Understanding dynamic allocation

    • Detecting memory management bugs with GCC 11, Part 2: Deallocation functions

    Recent Posts

    • Alternatives to creating bootc images from scratch

    • How to update OpenStack Services on OpenShift

    • How to integrate vLLM inference into your macOS and iOS apps

    • How Insights events enhance system life cycle management

    • Meet the Red Hat Node.js team at PowerUP 2025

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue