timeline

C++17- More Details about the Core Language

After I provided the big picture of the new C++17 core language in my post “C++17 – What’s New in the Core Language“, I will give you more details today. The details are mainly about inline variables, templates, automatic type deduction with auto, and attributes.

Here is the big picture of C++17 once more.

timeline

But let me write about the not-so-well-known feature.

Inline variables

Thanks to inline variables, the main reason for not packaging C++ code as header-only libraries are gone. You can declare global variables and static variables inline. The same rules applied to inline functions are applied to inline variables.

That means:

  • There can be more than one definition of an inline variable.
  • The definition of an inline variable must be present in the translation unit in which it is used.
  • A global inline variable (non-static inline variable) must be declared inline in every translation unit and has the same address in every translation unit.

Once more, the great benefit of inline variables. You can put your variables directly into your header files and include them more than once.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// widget.h

class Widget{
  public:
    Widget() = default;
    Widget(int w): width(w), height(getHeight(w)){}
    Widget(int w, int h): width(w), height(h){}

  private:
    int getHeight(int w){ return w*3/4; }
    static inline int width= 640;
    static inline int height= 480;
    static inline bool frame= false;
    static inline bool visible= true;

    ...
};

inline Widget wVGA;

 

auto can automatically deduce the type of its variable from its initializer. The story with auto goes on. Thanks to auto, template parameters of function templates and constructors (see C++17 – What’s New in the Core Language) can automatically be deduced from its arguments, and non-type template parameters can automatically be deduced from its template arguments. Irritated about the last part of my sentence? Irritated? That’s fine. I will write about the last part of my sentence in the next chapter.

Rainer D 6 P2 500x500

 

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.

     

    Automatic type deduction of non-type template parameters

    First of all. What are non-type template parameters? These are nullptr, integral, lvalue reference, pointer, and enumeration types. I will refer in this post mainly to integral types.

    After so much theory, let’s start with an example.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    template <auto N>
    class MyClass{
      ...
    };
    
    template <int N> 
    class MyClass<N> {
      ...
    };
    
    
    MyClass<'x'> myClass2;     // Primary template for char
    MyClass<2017>  myClass1;   // Partial specialisation for int
    

     

    By using auto in line 1 in the template signature,  N is a non-type template parameter. The compiler will automatically deduce it. A partial specialization for int is also possible: lines 6 and 7. The template instantiation in line 12 will use the primary template (lines 1-4), and the following template instantiation is the partial specialization for int.

    The usual type modifiers can be used to constrain the type of the non-type template parameters. Therefore, you don’t have to use partial specialization.

    template <const auto* p> 
    struct S;
    

    Here, p must be a pointer to const.

    The automatic type deduction for non-type template parameters works even for variadic templates.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <auto... ns>
    class VariadicTemplate{
      ...
    };
    
    template <auto n1, decltype(n1)... ns>
    class TypedVariadicTemplate{
      ...
    };
    

     

    Therefore, VariadicTemplate in lines 1-4 can deduce an arbitrary number of non-type template parameters. TypeVariadicTemplate will deduce only the first template parameter. The remaining templated parameters will be of the same type.

    The rules for auto in combination with a {}-Initialisation change in C++17.

    auto in combination with an {}-Initialisation

    Combining auto with an {}-Initialisation will give you a std::initializer_list.

      auto initA{1};          // std::initializer_list<int>
      auto initB= {2};        // std::initializer_list<int>
      auto initC{1, 2};       // std::initializer_list<int>
      auto initD= {1, 2};     // std::initializer_list<int>
    

     

    That was an easy-to-remember and to-teach rule. Sad to say; however, C++17 makes the feature from my perspective not better.

      auto initA{1};          // int
      auto initB= {2};        // std::initializer_list<int>
      auto initC{1, 2};       // error, no single element
      auto initD= {1, 2};     // std::initializer_list<int>
    

     

    Now, the rule is more complicated. Assigning with a {} returns a std::initializer_list. Copy construction only works for a single value.

    Now to a small but nice feature.

    Nested namespaces

    With C++17, you can quite comfortably define nested namespaces.

    Instead of writing

    namespace A {
      namespace B {
        namespace C {
          ...
        }
      }
    }
    

    you can simply write:

    namespace A::B::C {
      ...
    }
    

     

    C++17 has the three new attributes [[fallthrough]], [[nodiscard]], and [[maybe_unused]].

    The three new attributes fallthrough, nodiscard, and maybe_unused

    All three deal with compiler warnings. The examples are from cppreference.com.

    fallthrough

    [[fallthrough]] can be used in a switch statement. It has to be on its own line, immediately before a case label, indicating that a fall-through is intentional and should not diagnose a compiler warning.

    Here is a small example.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    void f(int n) {
      void g(), h(), i();
      switch (n) {
        case 1:
        case 2:
          g();
         [[fallthrough]];
        case 3: // no warning on fallthrough
          h();
        case 4: // compiler may warn on fallthrough
          i();
          [[fallthrough]]; // ill­formed, not before a case label
      }
    }
    

     

    The [[fallthrough]] attribute in line 7 suppresses a compiler warning. That will not hold for line 10. The compiler may warn. Line 12 is ill-formed because no case label is following.

    nodiscard

    [[nodiscard]] can be used in a function, enumeration, or class declaration. If you discard the return value from a function declared as nodiscard, the compiler should issue a warning. The same holds for a function returning an enumeration or a class, declared as nodiscard. A cast-to-void should not emit a warning.

    Therefore, line 5 should emit a warning, but line 10 should not produce a warning because the function foo returns by reference.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    struct [[nodiscard]] error_info { };
    error_info enable_missile_safety_mode();
    void launch_missiles();
    void test_missiles() {
       enable_missile_safety_mode(); // compiler may warn on discarding a nodiscard value
       launch_missiles();
    }
    error_info& foo();
    void f1() {
        foo(); // nodiscard type is not returned by value, no warning
    } 
    

    maybe_unused

    [[maybe_unused]] can be used to declare a class, a typedef­, a variable, a non­-static data member, a function, an enumeration, or an enumerator. Thanks to maybe_unused, the compiler suppresses a warning on an unused entity. 

     

    1
    2
    3
    4
    5
    6
    void f([[maybe_unused]] bool thing1,
           [[maybe_unused]] bool thing2)
    {
       [[maybe_unused]] bool b = thing1;
       assert(b); // in release mode, assert is compiled out
    }
    

     

    In release mode, line 5 is compiled out. That should produce no warning because b is declared as maybe_unused. The same holds for the variable thing2.

    What’s next?

    After my introduction to the core C++17 language ( C++17 – What’s New in the Core Language), I gave you more in this post more details. The same will hold for my next post. I will present in my next post more details to the new C++17 library. So, in case C++17 – What´s New in the Library made you curious.

     

    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 *