timeline

C++17 – What’s New in the Core Language?

C++11, C++14, and C++17. I guess you see the pattern.  Later this year, we will get a new C++ standard. In March 2017, the C++17 specification reached the Draft International Standard stage. Before I dive into the details, I will give you an overview of C++17.

 

Let me first look at the big picture.

The big picture

 

timeline 

Concerning C++98 to C++14, I only mentioned the big points. But, there is a C++ standard missing in my graphic: C++03. This is intentional because C++03 is a minimal C++ standard. More like a bug-fix release to C++98. If you know C++, you know, that the first ISO standard C++98 and the ISO standard C++11 are extensive standards. That will not hold for C++14 and in particular, for C++03.

So the question is. Is C++17 a big C++ standard or a small one? From my perspective, the answer is relatively easy. C++17 is something in between C++14 and C++11. So, C++17 is neither big nor small. Why? Here comes my short answer.

Overview

C++17 has a lot to offer. That will hold for the core language and the library. Let’s first look at the core language.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    Core language

    Fold expressions

    C++11 supports variadic templates. These are templates that can accept an arbitrary number of arguments. A parameter pack holds the arbitrary number. Additionally, with C++17, you can directly reduce a parameter pack with a binary operator:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // foldExpressionAll.cpp
    
    #include <iostream>
    
    template<typename... Args>
    bool all(Args... args) { return (... && args); }
    
    int main(){
    
      std::cout << std::boolalpha;
    
      std::cout << "all(): " << all() << std::endl;
      std::cout << "all(true): " << all(true) << std::endl;
      std::cout << "all(true, true, true, false): " << all(true, true, true, false) << std::endl;
    
      std::cout << std::endl;
    
    }
    

     

     The binary operator is the logical AND in line 6. Here is the output of the program.

    foldExpression

    That’s all I have to say about fold expressions because I have already written a post about fold expressions. So, there you have the details.

    We stay at compile time.

    constexpr if

    constexpr if it enables it to compile source code conditionally.

    1
    2
    3
    4
    5
    6
    7
    template <typename T>
    auto get_value(T t) {
        if constexpr (std::is_pointer_v<T>)
            return *t; // deduces return type to int for T = int*
        else
            return t;  // deduces return type to int for T = int
    }
    

     

    If T is a pointer, the if branch in line 3 will be compiled. If not, the else branch is in line 5. Two points are essential to mention. The function get_value has two different return types, and both branches of the if the statement has to be valid.

    Consequently, what is possible with for statements is with C++17 possible with if and switch statements.

    Initializers in if and switch statements

    You can directly initialize your variable inside the if and switch statement.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::map<int,std::string> myMap;
    
    if (auto result = myMap.insert(value); result.second){
        useResult(result.first);  
        // ...
    } 
    else{
        // ...
    } // result is automatically destroyed
    

     

    Therefore, the variable result is valid inside the if and else branches of the if statement. But result will not pollute the outer scope.

    If you use the initializer in if and switch statements in combination with the structured binding declaration, the C++ syntax will be more elegant.

    Structured binding declarations

    Thanks to the structured binding, you can bind a std::tuple or a struct directly to variables. Therefore I can still improve my last example.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::map<int,std::string> myMap;
                    
    if (auto [iter, succeeded] = myMap.insert(value); succeeded) {
        useIter(iter);  
        // ...
    }
    else{
        // ...
    } iter and succeded are automatically be destroyed
    

     

    auto [iter, succeeded] in line 3 automatically creates the two variables iter and succeeded. They will be destroyed at line 9.

    One of these features that make programming less cumbersome. The same holds for template deduction of constructors.

    Template deduction of constructors

    A function template can deduce its type parameters from its function arguments. But that was not possible for a unique function template: the constructor of a class template. With C++17, this statement is simply wrong. A constructor can deduce its type parameters from its constructor arguments.

     

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // templateArgumentDeduction.cpp
    
    #include <iostream>
    
    template <typename T>
    void showMe(const T& t){
      std::cout << t << std::endl;
    }
    
    template <typename T>
    struct ShowMe{
      ShowMe(const T& t){
        std::cout << t << std::endl;
      }
    };
    
    int main(){
      
      std::cout << std::endl;
        
      showMe(5.5);          // not showMe<double>(5.5);
      showMe(5);            // not showMe<int>(5);
        
      ShowMe<double>(5.5);  // with C++17: ShowMe(5.5);
      ShowMe<int>(5);       // with C++17: ShowMe(5);
      
      std::cout << std::endl;
        
    }
    

     

    Line 11 and 22 are possible in C++ since the first C++ standard. Lines 24 and 25 will be possible with C++17. Hence, you do not have to use angle brackets to instantiate a class template.

    There is not just usability. Additionally, we will get performance features.

    Guaranteed copy elision

    RVO stands for Return Value Optimisation and means that the compiler is allowed to remove unnecessary copy operations. What was until now a possible optimization step becomes in C++17 a guarantee.

    1
    2
    3
    4
    MyType func(){
      return MyType{};         // no copy with C++17
    }
    MyType myType = func();    // no copy with C++17
    

     

    Two unnecessary copy operations can happen in these few lines. The first one is in line 2 and the second one is in line 4. With C++17, both copy operations must go. 

    If the return value has a name, we call it NRVO. Maybe, you guessed it. This acronym stands for Named Return Value Optimization.

    1
    2
    3
    4
    5
    MyType func(){
      MyType myVal;
      return myVal;            // one copy allowed 
    }
    MyType myType = func();    // no copy with C++17
    

     

    The subtle difference is that the compiler can still copy the value myValue according to C++17 (line 3). But no copy will take place in line 5.

    If a feature is unnecessary or its application is even dangerous, you should remove it. This will happen in C++17 with std::auto_ptr and trigraphs.

    auto_ptr and trigraphs removed

    auto_ptr

    std::auto_ptr is the first smart pointer in C++. Its job is to take care of one resource. But it had a big issue. If you copy a std::auto_ptr, a move operation will occur under the hood. That is the reason we get std::unique_ptr with C++11 as the replacement. You can not copy a std::unique_ptr.

     

    1
    2
    3
    4
    5
    6
    std::auto_ptr<int> ap1(new int(2011));
    std::auto_ptr<int> ap2= ap1;              // OK     (1)
    
    std::unique_ptr<int> up1(new int(2011));
    std::unique_ptr<int> up2= up1;            // ERROR  (2)
    std::unique_ptr<int> up3= std::move(up1); // OK     (3)
    

     

    Trigraphs

    Trigraphs are a sequence of three characters in the source code that are treated as a single character. They will be necessary if your keyboard doesn’t support single characters.

    If you want to write obfuscated code, C++17 maybe not be your language anymore.

     

    1
    2
    3
    4
    5
    6
    7
    // trigraphs.cpp
    
    int main()??<
    
      ??(??)??<??>();
    
    ??>
    

     

    I guess, you know, what the program is doing? If not, you have to translate the trigraphs to their single-character representation.

     trigraph

    If you apply the table, you will solve the riddle. The program represents a lambda function that will be executed just in place.

     

    1
    2
    3
    4
    5
    6
    7
    // trigraphsLambda.cpp
    
    int main(){
    
      []{}();
    
    }
    

     

    What’s next?

    That is easy. In the next post, I will write about the library feature we get with C++17. These are the string_view, the parallel STL, and the filesystem library. Additionally, we will get the new data types std::any, std::optional, and std::variant.

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *