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






4.92/5 (34 votes)
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++ 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++ 14
int a = 1'000'000;
+1. Makes reading large numbers (especially 64 bit pointers) easier.
Variable Templates
// 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++ 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++ 14
auto lambda = [](auto x, auto y) {return x + y;};
+1. Auto enables you to type less.
Namespace Nesting
// C++ 11
namespace A
{
namespace B
{
namespace C
{
}
}
}
// C++ 17
namespace A::B::C
{
}
+1. Significantly less typing.
Structure Binding
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++ 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++ 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++ 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++ 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++ 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++ 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++ 17
template <auto value> void foo() { }
foo<3>(); // deduces int
+1, autos anywhere. :)
Some New Attributes
// 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++ 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++ 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++ 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++ 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++ 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++ 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
- Lambda capture of *this - http://www.bfilipek.com/2017/01/cpp17features.html#lambda-capture-of-this
- C++14 - https://en.wikipedia.org/wiki/C%2B%2B14
- C++17 - https://en.wikipedia.org/wiki/C%2B%2B17
- http://libcxx.llvm.org/cxx1z_status.html
- C++17 implementation status - http://clang.llvm.org/cxx_status.html#cxx17
- What are the new features in C++17? - https://stackoverflow.com/questions/38060436/what-are-the-new-features-in-c17
History
- 3rd January, 2018 : First release, happy new year!