18.advanced Topics I
18.advanced Topics I
Programming
18. Advanced Topics I
Federico Busato
2023-11-14
Table of Context
1 Move Semantic
lvalues and rvalues references
Move Semantic
std::move
Class Declaration Semantic
1/56
Table of Context
3 Value Categories
2/56
Table of Context
6 Type Deduction
Pass-by-Reference
Pass-by-Pointer
Pass-by-Value
auto Deduction
7 const Correctness
3/56
Move Semantic
Overview
4/56
lvalue and rvalue 1/3
5/56
lvalues and rvalues 2/3
6/56
lvalues and rvalues 3/3
struct A {};
A a;
f(a); // ok, f() can modify "a"
g(a); // ok, f() cannot modify "a"
// h(a); // compile error f() does not accept lvalues
# include <algorithm>
class Array { // Array Wrapper
public:
Array() = default;
# include <vector>
int main() {
std::vector<Array> vector;
vector.push_back( Array{1000} ); // call push back(const Array&)
} // expensive copy
9/56
Move Semantic 1/3
∼X(); // destructor
};
10/56
Move Semantic 2/3
Move constructor
Array(Array&& obj) {
_size = obj._size; // (1) shallow copy
_array = obj._array; // (1) shallow copy
obj._size = 0; // (2) release obj (no more valid)
obj._array = nullptr; // (2) release obj
}
Move assignment
Array& operator=(Array&& obj) {
delete[] _array; // (1) release this
_size = obj._size; // (2) shallow copy
_array = obj._array; // (2) shallow copy
obj._array = nullptr; // (3) release obj
obj._size = 0; // (3) release obj
return *this; // (4) return *this 12/56
}
std::move
int main() {
std::vector<Array> vector;
vector.push_back( Array{1000} ); // call "push back(Array&&)"
Array arr{1000};
vector.push_back( arr ); // call "push back(const Array&)"
If an object requires the copy constructor/assignment, then it should also define the
move constructor/assignment. The opposite could not be true
14/56
Move Semantic and Code Reuse
15/56
Class Declaration Semantic - Compiler Implicit
17/56
Universal Reference
and Perfect
Forwarding
Universal Reference 1/3
The && syntax has two different meanings depending on the context it is used
• rvalue reference
• Universal reference: Either rvalue reference or lvalue reference
Universal references (also called forwarding references) are rvalues that appear in a
type-deducing context. T&& auto&& accept any expression regardless it is an lvalue
or rvalue and preserve the const property
template<typename T>
void f2(T&& t) {} // universal reference
19/56
Universal Reference 3/3
struct A {};
void f1(A&& a) {} // rvalue only
template<typename T>
void f2(T&& t) {} // universal reference
A a;
f1(A{}); // ok
// f1(a); // compile error (only rvalue)
f2(A{}); // universal reference
f2(a); // universal reference
A&& a2 = A{}; // ok
// A&& a3 = a; // compile error (only rvalue)
auto&& a4 = A{}; // universal reference
auto&& a5 = a; // universal reference
20/56
Universal Reference - Misleading Cases
template<typename T>
void f(std::vector<T>&&) {} // rvalue reference
template<typename T>
void f(const T&&) {} // rvalue reference (const)
21/56
Reference Collapsing Rules
22/56
Perfect Forwarding
struct A{};
f ( A{10} ); // print "rvalue"
g1( A{10} ); // print "lvalue"!!
g2( A{10} ); // print "rvalue"
23/56
Value Categories
Taxonomy (simplified)
24/56
Taxonomy 1/2
lvalue is a glvalue but it is not movable (it is not an xvalue). An named rvalue
reference is a lvalue
26/56
Examples
struct A {
int x;
};
void f(A&&) {}
A&& g();
//----------------------------------------------------------------
int a = 4; // "a" is an lvalue, "4" is a prvalue
f(A{4}); // "A{4}" is a prvalue
A c{4};
f(std::move(c)); // "std::move(c)" is a xvalue
f(A{}.x); // "A{}.x" is a xvalue
g(); // "A&&" is a xvalue
27/56
&, && Ref-qualifiers
and volatile
Overloading
&, && Ref-qualifiers Overloading 1/3
A a1;
a1.f(); // call "f() &"
const A a1;
a1.f(); // call "f() const &"
29/56
&, && Ref-qualifiers Overloading 3/3
30/56
volatile Overloading
struct A {
void f() {}
void f() volatile {} // e.g. propagate volatile to data members
void f() const volatile {}
// void f() volatile & {} // combining ref-qualifier and volatile
// void f() const volatile & {} // overloading is also fine
// void f() volatile && {}
// void f() const volatile && {}
};
volatile A a1;
a1.f(); // call "f() volatile"
31/56
Member Function Overloading: Choices You Didn’t Know You Had
Copy Elision and
RVO
Copy Elision and RVO
32/56
RVO Example
If provided, the compiler uses the move constructor instead of copy constructor
33/56
RVO - Where it works
RVO Copy elision is always guarantee if the operand is a prvalue of the same class
type and the copy constructor is trivial and non-deleted
struct Trivial {
Trivial() = default;
Trivial(const Trivial&) = default;
};
// sigle instance
Trivial f1() {
return Trivial{}; // Guarantee RVO
}
// distinct instances and run-time selection
Trivial f2(bool b) {
return b ? Trivial{} : Trivial{}; // Guarantee RVO
}
34/56
Guaranteed Copy Elision (C++17)
In C++17, RVO Copy elision is always guarantee if the operand is a prvalue of the
same class type, even if the copy constructor is not trivial or deleted
struct S1 {
S1() = default;
S1(const S1&) = delete; // deleted
};
struct S2 {
S2() = default;
S2(const S2&) {} // non-trivial
};
Obj f1() {
Obj a;
return a; // most compilers apply NRVO
}
Obj f2(bool v) {
Obj a;
if (v)
return a; // copy/move constructor
return Obj{}; // RVO
}
36/56
RVO Example - Where it does NOT work 2/3
Obj f3(bool v) {
Obj a, b;
return v ? a : b; // copy/move constructor
}
Obj f4() {
Obj a;
return std::move(a); // force move constructor
}
Obj f5() {
static Obj a;
return a; // only copy constructor is possible
}
37/56
RVO Example - Where it does NOT work 3/3
Obj f6(Obj& a) {
return a; // copy constructor (a reference cannot be elided)
}
Obj f9(Obj&& a) {
return a; // copy constructor (the object is instantiated in the function)
}
38/56
Type Deduction
Type Deduction
When you call a template function, you may omit any template argument that
the compiler can determine or deduce (inferred) by the usage and context of
that template function call [IBM]
• The compiler tries to deduce a template argument by comparing the type of the
corresponding template parameter with the type of the argument used in the func-
tion call
• Similar to function default parameters, (any) template parameters can be deduced
only if they are at end of the parameter list
39/56
Example
template<typename T>
int add1(T a, T b) { return a + b; }
add1(1, 2); // ok
// add1(1, 2u); // the compiler expects the same type
add2(1, 2u); // ok (add2 is more generic)
add3<int, 2>(1); // "int" cannot be deduced
40/56
add4<2>(1); // ok
Type Deduction - Pass by-Reference
template<typename T>
void g(const T& a) {}
int x = 3;
int& y = x;
const int& z = x;
f(x); // T: int
f(y); // T: int
f(z); // T: const int // <-- !! it works...but it does not
g(x); // T: int // for "f(int& a)"!!
g(y); // T: int // (only non-const references)
g(z); // T: int // <-- note the difference 41/56
Type Deduction - Pass by-Pointer 1/2
template<typename T>
void g(const T* a) {}
int* x = nullptr;
const int* y = nullptr;
auto z = nullptr;
f(x); // T: int
f(y); // T: const int
// f(z); // compile error!! z: "nullptr_t != T*"
g(x); // T: int
g(y); // T: int <-- note the difference
42/56
Type Deduction - Pass by-Pointer 2/2
template<typename T>
void f(const T* a) {} // pointer to const-values
template<typename T>
void g(T* const a) {} // const pointer
int* x = nullptr;
const int* y = nullptr;
int* const z = nullptr;
const int* const w = nullptr;
f(x); // T: int
f(y); // T: int
f(z); // T: int
// g(x); // compile error!! objects pointed are not constant
// g(y); // the same (the pointer itself is constant)
g(z); // T: int
43/56
g(w); // T: const int
Type Deduction - Pass by-Value 1/2
template<typename T>
void g(const T a) {}
int x = 2;
const int y = 3;
const int& z = y;
f(x); // T: int
f(y); // T: int!! (drop const)
f(z); // T: int!! (drop const&)
g(x); // T: int
g(y); // T: int
g(z); // T: int!! (drop reference) 44/56
Type Deduction - Pass by-Value 2/2
template<typename T>
void f(T a) {}
int* x = nullptr;
const int* y = nullptr;
int* const z = x;
f(x); // T = int*
f(y); // T = int* !! (const drop)
f(z); // T = int* const
45/56
Type Deduction - Array
template<typename T>
void g(T array) {}
46/56
Type Deduction - Conflicts 1/2
template<typename T>
void add(T a, T b) {}
template<typename T>
void add(T a, char b) {}
47/56
Type Deduction - Conflicts 2/2
template<typename T>
void f(T* array) {}
// template<typename T>
// void f(T array) {} // ambiguous
int x[3];
f(x); // call f(T*) not f(T(&)[3]) !!
48/56
auto Deduction
int v = 3;
int x1 = f1(v);
int& x2 = f2(v);
// int& x3 = f3(v); // compile error 'x' is copied by-value
int& x4 = f4(v); 49/56
const Correctness
const Correctness
References:
• Isocpp: const-correctness
• GotW: Const-Correctness
• Abseil: Meaningful ‘const’ in Function Declarations
• const is a contract
• Why const Doesn’t Make C Code Faster
• Constant Optimization?
50/56
Basic Rules 1/2
• const entities do not change their values at run-time. This does not imply that
they are evaluated at compile-time
• const T* is different from T* const . The first case means “the content does
not change”, while the later “the value of the pointer does not change”
• Pass by-const-value and by-value parameters imply the same function signature
51/56
Basic Rules 2/2
• mutable fields can be modified by a const member function (they should not
change the external view)
• const keyword purpose is for correctness (type safety ), not for performance
• const may provide performance advantages in a few cases, e.g. non-trivial copy
semantic
52/56
Function Declarations Example
void f(int);
void f(const int); // the declaration is exactly the same of
// "void f(int)"!!
void f(int*);
void f(const int*); // different declaration
void f(int&);
void f(const int&); // different declaration
int f();
// const int f(); // compile error conflicting declaration
53/56
const Return Example
struct A {
void f() { cout << "non-const"; }
void f() const { cout << "const"; }
};
54/56
getA().f(); // print "const"
struct Example
void f(A a) {
a.value = 3;
a.ptr[0] = 3;
}
void g(const A a) { // the same with g(const A&)
// a.value = 3; // compile error
a.ptr[0] = 3; // "const" does not apply to "ptr" content!!
}
A a{new int[10]};
f(a);
g(a); 55/56
Member Functions Example
struct A {
int value = 0;
56/56