C++17- More Details about the Core Language

Contents[Show]

After I provided the big picture to the new C++17 core language in my post "C++17 - What's New in the Core Language", I will give you today more details. 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 to not packaging C++ code as header-only libraries are gone. You can declare global variables and static variables inline. The same rules that are applied to inline functions are applied to inline variables.

That means:

  • There can be more than one definitions 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 you 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 initialiser. 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 it template arguments. Irritate 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.

Automatic type deduction of non-type template parameters

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

After so much theory, lets 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 specialisation for int is also possible: line 6 and 7. The template instantiation in line 12 will use the primary template (line 1-4) and the following template instantiation the partial specialisation for int.

The usual type modifiers can be used to constrain the type of the non-type template parameters. Therefore, you have not to use partial specialisation.

template <const auto* p> 
struct S;

Here, p must be a pointer to const.

The automatic type deduction for non-type template parameter 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 line 1-4 can deduce an arbitrary number of non-type template parameter. 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

If you use auto in combination with an {}-Initialisation, you will get 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 make 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 comfortable 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 and indicates that a fall through is intentional and should therefore no 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 declaration, enumeration declaration, 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 in the declaration of 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 hold 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 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.

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

Tags: C++17

Add comment


My Newest E-Book

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 555

All 255860

Currently are 158 guests and no members online