Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++17

From C++ 11 to C++ 17: A Walkthrough

4.92/5 (35 votes)
5 Jan 2018CPOL3 min read 27.7K  
The most appealing (to me!) new C++ features

Introduction

This small article contains 20 features since C++ 11 and up to C++ 17 that appealed to me, so I present them to you, along with some nasty comments. Happy reading! This article will be updated constantly.

Background

Yes, assume full C++ 11 knowledge. This is not a beginner's article.

The Features

New and Updated Literals


C++
// C++ 14 Binary literals
int a = 0b111; // a = 7

// C++ 14 Prefixes
auto str = "hello"s; // auto deduces string
auto dur = 60s;      // auto deduces chrono::seconds
auto z   = 1i;       // auto deduces complex<double>

+1, although I would somewhat prefer to not excessively use auto everywhere.

Digit Separators


C++
// C++ 14
int a = 1'000'000; 

+1. Makes reading large numbers (especially 64 bit pointers) easier.

Variable Templates


C++
// C++ 14
template <typename T> T pi = (T)3.141592;
double x = sin(2.0*440.0*pi<double>*0.01); // specializes pi as double
float perimeter = 2.0f*pi<float>*radius;   // specializes pi as float

=0. I haven't really found anything useful for it yet. Templating a function or a class OK - you want to use it with numerous types. Templating a variable means that I have the same name for actually different variables - ouch. Actually, the only example I've found online is the pi version.

Function Return Auto Deduction


C++
// C++ 14
auto foo(int j,int x)
{
    return j + x; // "auto" -> "int";
}

auto boo();

auto tu() // better than tuple<int,int,int> tu()
{
    return make_tuple<int,int,int>(5,6,7);
}

int main()
{
    auto t = tu();    // t = tuple(5,6,7)
    int a = foo(5,6); // OK
    int b = boo();    // Error
}

+1. Mostly useful for lengthy-types like the tuple above. If there are multiple return statements, all of them must deduce the same type. Since this is a single-pass, the deduction must be available when it is needed. For example, the int b = boo() line will fail because boo hasn't been defined yet, even if the line suggests an int-value. Defining the function in another TU, or even in the same TU but below that code will fail.

Related Lambdas


C++
// C++ 14
auto lambda = [](auto x, auto y) {return x + y;};

+1. Auto enables you to type less.

Namespace Nesting


C++
// C++ 11
namespace A 
{
    namespace B
    {
        namespace C
        {
        }
    }
}

// C++ 17
namespace A::B::C
{
}

+1. Significantly less typing.

Structure Binding


C++
tuple<int,float,char> x() { return make_tuple<int,float,char>(5,5.2f,'c'); }

// C++ 11
int a;
float b;
char c;
std::tie(a,b,c) = x();

// C++ 17
auto [a,b,c] = x();

+1. It also works in structures. The problem is that you can't use std::ignore, like in the old tie.

std::optional


C++
// C++ 17
using namespace std;
optional<string> foo()
{
    if (something)
       return "Hello";
    return {};
}

int main()
{
    auto rv = foo();
    if (rv) 
    {
         // We have a value, rv.value();
    }
    rv.value_or("dada"); // Either the value, or "dada"
}

+1. Allows a "nothing" return value to be returned while providing a catchall case with value_or. HOWEVER, this does not work as intended:

C++
// C++ 17
using namespace std; 
optional<int> foo(int x) 
{ 
if (x == 1)
   return 5; 
}

Now I still get a compiler warning, and probably an undefined-behaviour std::optional. When there is a non returning path, return {} should be implied (Yes, I've proposed it and yes, they all disagree with me. Who cares. :))

std::any


C++
// C++ 17
using namespace std;
any foo()
{
    string h("hello");
    return any(h);
}

int main()
{
    try 
    {
        auto r = any_cast<string>(foo());
    }
    catch(...)
    {
    }
}

+1 I do not like it much, but I have found a nice trick with it.

std::variant


C++
// C++ 11
union A
{
    int a;
    float b;
    char c;
};

A x;
x.a = 5; 
float b = x.b; 

// C++ 17
using namespace std;
variant<int,float,char> x;
x = 5;                    // now contains int
int i = std::get<int>(v); // i = 5;
std::get<float>(v);       // Throws

=0, std::variant is not a normal union, but a type-safe union, because it throws when you try to access the non-current type. However, the point of the old-type unions is to share memory without types (consider the old x86 union ax { struct {char al,char ah}; short ax; }). I'm not very sure of a useful case scenario.

Fold Expressions


C++
// C++ 11 add numbers
template <typename T>
T add(T t) { return t; }

template <typename T, typename... Many>
T add(T t, Many... many) {
  return t + add(many...);
}

// C++ 17 fold expressions
template <typename T, typename ... Many>
T add(T t, Many... many)
{
   return (t + ... + many);
}

// ----
int main()
{
  auto r = add(1,2,3,4,5,6,7,8,9,10); // r = 55, 
                                      // but in C++ 17 method we only need one function.
}

+100. No comments. The idea is to combine the ellipsis with one of the operators to produce a non-comma expansion of the pack.

#if has_include


C++
// C++ 17
#if __has_include(<string>)
// Assume <string> has been included
#else
#endif

=0. It's OK, generalizes old #pragma once and #IFNDEF _STRING_H stuff.

template <auto>


C++
// C++ 17
template <auto value> void foo() { }
foo<3>();               // deduces int

+1, autos anywhere. :)

Some New Attributes


C++
// C++ 14
// [[deprecated("reason")]]

void [[deprecated("This function is not safe")]] foo ()
{
}


// C++ 17
// [[fallthrough]]
// Notifies the compiler that a lack of break is intentional

switch(a)
{
    case 1:
       test();          // Warning issued, did the programmer forget the break statement?
    case 2: 
       test2();
       [[fallthrough]]; // No warning, the lack of break was intentional
    case 3: 
       ////
}

// [[nodiscard]] 
// Issues a warning when a return value of a function is discarded

[[nodiscard]] int foo()
{
    return 1;
}

int main()
{
   foo();              // issues a warning
   int a = foo();      // not a warning now
}

// [[maybe_unused]]
int foo()
{
    [[maybe_unused]] int y;
    // No warning later if y is not used. Something like UNREFERENCED_PARAMETER() macro
}

+1, they are nice standarizations for older pragma's.

if and switch init Before Conditions


C++
// C++ 11
int val = foo();
if (val == 1)
{
}

// C++ 17
if (int val = foo() ; val == 1)
{
}

+1, the difference is that the variable's scope is inside the if only.

Inline Variables


C++
// C++ 17
inline int x = 5;
​​​​​

+1000, now you can include also variables in .h files without problem. It can be included as many times as needed and it's only defined once.

Auto Deduction from Initializer Lists


C++
// C++ 11
std::initializer_list<int> x1 = { 1, 2 };

// C++ 17
auto x1 = { 1, 2 }; // automatically std::initializer_list<int>

+1. Nice auto again.

Constructor Template Deduction


C++
// C++ 11
auto p = std::pair<double,int>(5.0,0);

// C++ 17
auto p = std::pair(5.0,0); // deduces double,int

+1000, it was about time.

Exception Specifications are Part of the Type


C++
// C++ 11
void (*p)() throw(int);       // a function pointer to a function that throws an int
void (**pp)() throw() = &p;   // a function pointer to a function that does not throw

+1. In C++ 17, the above is an error. The exception specification throw(int) is now part of the type.

And finally.....

string_view


C++
// C++ 17
using namespace std;

string a = "hello there";
// a uses dynamic memory 

string_view largeStringView{a.c_str(), a.size()};

+1000. A string_view gives us all the benefits of std::string but on a string that is already owned (either be another string or a char array). Therefore, it can be used in non-mutable situations (like comparisons or substrings).

Acknowledgments

History

  • 3rd January, 2018 : First release, happy new year!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)