[TOC]
# Modern C++ (since C++11)
"If you're an experienced C++ programmer and are anything like me, you initially approached C++11 thinking, "Yes, yes, I get it. It's C++, only more so." But as you learned more, you were surprised by the scope of the changes. **auto** declarations, **range-based for** loops, **lambda** expressions, and **rvalue references** change the face of C++, to say nothing of the new concurrency features. And then there are the idiomatic changes. 0 and typedefs are out, nullptr and alias declarations are in. **Enums** should now be scoped. **Smart pointers** are now preferable to built-in ones. **Moving objects** is normally better than copying them.
\- Effective Modern C++ by Scott Meyers
## auto
## lambda expressions
## initialization
### uniform initializaion list\[[1](https://www.mica.edu.vn/perso/Vu-Hai/EE3490/Ref/The%20C++Standard%20Library%20-%202nd%20Edition.pdf)\]
An initializer list forces so-called value initialization, which means that even local variables of fundamental data types, which usually have an undefined initial value, are initialized by zero (or nullptr, if it is a pointer):
```C++
int i; // i has undefined value
int j{}; // j is initialized by 0
int* p; // p has undefined value
int* q{}; // q is initialized by nullptr
```
Note, however, that narrowing initializations — those that reduce precision or where the supplied
value gets modified— are not possible with braces. For example:
```C++
int x1(5.3); // OK, but OUCH: x1 becomes 5
int x2 = 5.3; // OK, but OUCH: x2 becomes 5
int x3{5.0}; // ERROR: narrowing
int x4 = {5.3}; // ERROR: narrowing
char c1{7}; // OK: even though 7 is an int, this is not narrowing
char c2{99999}; // ERROR: narrowing (if 99999 doesn’t fit into a char)
std::vector<int> v1 { 1, 2, 4, 5 }; // OK
std::vector<int> v2 { 1, 2.3, 4, 5.6 }; // ERROR: narrowing doubles to ints
```
##### std::initializer_list
To support the concept of initializer lists for user-defined types, C++11 provides the class template std::initializer_list<>. It can be used to support initializations by a list of values or in any other place where you want to process just a list of values.
```C++
class P
{
public:
P(int, int){std::cout<<"call P::P(int,int)"<<std::endl;}
P(std::initializer_list<int>){
std::cout<<"call P::P(initializer_list)"<<std::endl;
}
};
P p(77,5); // call P::P(int,int)
P q{77,5}; // call P::P(initializer_list)
P r{77,5,42}; // call P::P(initializer_list)
P s = {77, 5}; // call P::P(initializer_list)
```
Without the constructor for the initializer list, the constructor taking two ints would be called to initialize q and s, while the initialization of r would be invalid.
### In-class initializers
More easily ensures that all the members are initialized in all the constructors.
```C++
class Trivial {
public:
Trivial() = default;
// some other constructors
private:
int i_{0};
int j_{42};
};
```
All constructors which do not specify an initial value for i_ or j_ are using the in-class specified one.
## Range loop
Allow to iterate on a STL containers
```C++
int sum{0};
for (auto&& i: getValues()) {
sum += i;
}
```
## Keyword decltype
By using the new decltype keyword, you can let the compiler find out the type of an expression. This is the realization of the often requested typeof feature. However, the existing typeof implementations were inconsistent and incomplete, so C++11 introduced a new keyword.
```C++
std::map<std::string,float> coll;
decltype(coll)::value_type elem;
```
One application of decltype is to declare return types (see below). Another is to use it in metaprogramming
(see Section 5.4.1, page 125) or to pass the type of a lambda (see Section 10.3.4, page 504).
## keyword constexpr
* constexpr indicates a value that's constant and known during compilation.
* const indicates a value that's only constant; it's not compulsory to know during compilation.
```C++
int sz;
constexpr auto arraySize1 = sz; // error! sz's value unknown at compilation
std::array<int, sz> data1; // error! same problem
constexpr auto arraySize2 = 10; // fine, 10 is a compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2 is constexpr
const auto arraySize3 = sz; // fine, arraySize is const copy of sz
std::array<int, arraySize3> data; // error! arraySize's value unknown at compilation
```
* The constexpr specifier declares that it is possible to evaluate the value of the function or variable at compile time
```C++
struct S {
constexpr S(int);
};
const S s0(0); // constructor called at runtime
constexpr S s1(1); // constructor called at compile time
```
## keyword 'override' & 'final' in C++ as class specifier
The override[C++11] keyword serves two purposes:
1. It shows the reader of the code that "this is a virtual method, that is overriding a virtual method of the base class.
2. The compiler also knows that it's an override, so it can "check" that you are not altering/adding new methods that you think are overrides.
```C++
class Base {
public:
virtual void f();
virtual void g();
void h();
};
class Derived: Base {
public:
void f() override;
void g() final;
void h() override; // error as Base::f is not virtual
}
class DerivedTwice: Derived {
public:
void f(int i) override; // error: parameter list is different to base declaration
void g() override; // error as Derived::g is final
};
```
In DerivedTwice::f the compiler will issue an error for "changing the type". Without override, at most the compiler would give a warning for "you are hiding virtual method by same name".
## Enum class (Scoped Enumerations)
Prefer scoped enum class to simple unscoped enum:
Avoid name conflicts
Avoid implicit conversion to int
```C++
enum class Color { red, green = 20, blue };
Color r = Color::blue;
switch(r) {
case Color::red :
std::cout << "red\n";
break;
case Color::green:
std::cout << "green\n";
break;
case Color::blue :
std::cout << "blue\n";
break;
}
int n = r; // error: no scoped enum to int conversion
Color r2 = 20; // error: no conversion from int to Color
int n = static_cast<int>(r); // OK, n = 21
```
# move
to understand move, we need to understand lvalue & rvalue first.
## lvalue & rvalue
* lvalues and rvalues are not restricted to assignment statements; that's just where the names originate. It turns out that every value in your code is an lvalue or rvalue.
> To distinguish the two easily, ask yourself, "Does it have a name?" If it does, it's an lvalue (except for enumerators in an enumeration or non-type template parameters). Otherwise, it's an rvalue.
* Lvalues are addressable and rvalues are not. For instance, you can write &a, but not &42.
> a = 42; // lvalue = rvalue
* Before C++11, the notions of lvalue- and rvalue-ness of values was important to compiler writers, but there was no way to express such things in user code.
>C++11 introduced rvalue references to enable move semantics and enable perfect forwarding.
* "Old style", "normal" references are lvalue references, written like T & or T const &.
>Rvalue references use a double ampersand, like T && or T const &&.
## move
https://vlsj-cqi01.cadence.com/tzlaine_talks/rvalues/slides/#/0/34
* If you write any of these operations, you probably need to write all of them, unless your type is move-only, or doesn't have a move that is more efficient than copy: copy constructor, move constructor, assignment operator, move assignment operator, and destructor.
* Use std::move() on an lvalue to treat it as an rvalue; remember that this is only a cast.
* An object with a name is an lvalue (except enumerators and non-type template parameters).
* Rvalue reference parameters have names.
* In general, a moved-from object can only be assigned to and destructed.
* For some template parameter T, a function parameter T && x may be a universal reference or an rvalue reference, depending on whether the template-head is on the function that takes x or not.
```C++
template<typename T>
void foo(T && x); // universal reference
template<typename T>
struct foo {
void bar(T && x); // rvalue reference
};
```
* Pass sink parameters (parameters that are always copied or moved from) by value.
```C++
std::string append_if_unique(std::string str, std::string const & suffix)
{
if (str.find(suffix) == std::string::npos)
str += suffix;
return str;
}
std::string foo = /* ... */;
foo = append_if_unique(foo, "aaa");
auto bar = append_if_unique(std::move(foo), "bbb");
```
* When returning values, just use return x. Don't use std::move(); it will only cause problems.
## nullptr: Use nullptr instead of using 0 or NULL
* nullptr has advantages over both 0 and NULL. It is clearly mark the intend to have a null pointer value like NULL but won’t induce into error about its true type.
```C++
void f(leWireElement* elem) {}
void f(int) {}
f(NULL) // call f(int)
f(nullPtr) // call f(leWireElement* elem)
```
* Don’t test pointer as if they were boolean
```C++
if (ptr) {
…
}
//should be ->
if (ptr != nullptr) {
…
}
```
* [C++11 nullptr](https://shengyu7697.github.io/cpp-nullptr/)
## 3.1.12 New Function Declaration Syntax[1]
Sometimes, the return type of a function depends on an expression processed with the arguments. However, something like
```C++
template <typename T1, typename T2>
decltype(x+y) add(T1 x, T2 y);
```
was not possible before C++11, because the return expression uses objects not introduced or in scope yet. But with C++11, you can alternatively declare the return type of a function behind the parameter list:
```C++
template <typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x+y);
```
## references
1. [C++ STL 2nd Edition](https://www.mica.edu.vn/perso/Vu-Hai/EE3490/Ref/The%20C++Standard%20Library%20-%202nd%20Edition.pdf)
2. [Modern C++ (Lecture & Tutorials)](https://www.youtube.com/playlist?list=PLgnQpQtFTOGRM59sr3nSL8BmeMZR9GCIA)
3. [Modern C++ Tutorial: C++ 11/14/17/20 On the Fly](https://changkun.de/modern-cpp/en-us/00-preface/)
4. [What is Moden C++?](https://www.modernescpp.com/index.php/what-is-modern-c#:~:text=Modern%20C%2B%2B%20stands%20for%20C%2B%2B,14%2C%20and%20C%2B%2B17.&text=If%20you%20look%20at%20the,the%20second%20modern%20C%2B%2B.)
5. https://github.com/PacktPublishing/Complete-Modern-C-/tree/master/Section%2001