1.1 (1)
Introduction

Modern C++: Move semantics

A series of workshops covering modern features of C++

1.2 (2)
Introduction

Series overview

4 workshops:

  • Fundamentals
  • Templates
  • Move semantics & rvalue references
  • Metaprogramming
1.3 (3)
Introduction

Agenda

  1. Lecture: Move semantics (practical)
  2. Lecture: rvalue references (theoretical)
  3. Lecture: Perfect forwarding (mixed)
  4. Exercises: 1 quiz, 2 exercises.
1.4 (4)
Introduction

Before we start

Get latest version of Visual Studio 2017!

Contact information:

E-mail 143042@nhtv.nl
Slack Caspar143042
Discord Aidiakapi#2177

Please give me feedback! https://goo.gl/forms/zVXOH97o1iyB4qIj1

2.1 (5)
Move Semantics

Move Semantics

What is it, why should you care?

2.2 (6)
Move Semantics

When copying makes no sense

Some objects should logically not be copied.

1234567891011121314
struct Player /* stuff here */ };
struct Game
{
    std::unique_ptr<Playerplayer;
};
static void example()
{
    auto player = std::make_unique<Player>();
    Game game{ std::move(player) };
What happens?
Moving a unique_ptr instead transfers ownership. player == nullptr
    // error C2280: 'std::unique_ptr<Player,std::default_delete<_Ty>>::unique_ptr
    //              (const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)'
    //              : attempting to reference a deleted function
Signature: T(T const&)
What is that called?
Trying to call the copy constructor.
}

Copying a std::unique_ptr is not allowed. Why?

It is a unique owner of the resource. Copying would imply at least 2 owners.

A std::unique_ptr can be moved instead!

2.3 (7)
Move Semantics

When it makes no sense to copy

Some objects you don't want to copy.

123456789101112
struct Bullet /* bullet data here */ };
struct Game
{
    std::vector<Bulletbullets;
};
static void example()
{
    std::vector<Bullet> bullets;
    // Inserts 1000 bullets here
    Game game{ std::move(bullets) };
What happens to bullets?
1000 bullets get copied.
Moving the vector is basically a pointer-swap.
}

Moving here avoids an unnecessary copy.

The state of bullets is unspecified but valid.[SO]

2.4 (8)
Move Semantics

Motivation

Reasons for moving:

  1. When a type logically cannot be copied.
    • std::unique_ptr
    • std::unique_lock
    • std::basic_istream & std::basic_ostream
    • Objects living in VRAM.
    • Network sockets.
  2. Avoid copies to improve performance.
    • Any copyable resource.
    • Collections.
2.5 (9)
Move Semantics

Copy semantics

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
class Surface
{
public:
    Surface() noexcept :
        width_ },
        height_},
        buffer_nullptr }
    { }
    Surface(int32_t const width, int32_t const height)
    {
        if (width <= || height <= 0)
        {
            width_ height_ 0;
            buffer_ nullptr;
            return;
        }
        size_t const byte_count = width * height * sizeof(Pixel);
        width_  = width;
        height_ = height;
        buffer_ static_cast<Pixel*>(_aligned_malloc(byte_count, 16));
Compiler specific!
What if the allocation fails?
        if (buffer_ == nullptr)
        {
            throw std::bad_alloc{};
        }
        std::fill_n(buffer_width_ height_0);
    }
    Surface(Surface const& rhs) :
        Surface{ rhs.width_, rhs.height_ }
    {
        if (buffer_ != nullptr)
        {
            std::copy_n(rhs.buffer_width_ height_buffer_);
        }
    }
    Surfaceoperator=(Surface const& rhs)
    {
        auto copy = rhs;
        swap(copy);
        return *this;
    }
    Surface(Surface&& rhs) noexcept :
        Surface{}
    {
        swap(rhs);
    }
    Surfaceoperator=(Surface&& rhs) noexcept
    {
        auto move = std::move(rhs);
        swap(move);
        return *this;
    }
    ~Surface() noexcept
    {
        if (buffer_ != nullptr)
        {
            _aligned_free(buffer_);
        }
    }
    void swap(Surface& rhs) noexcept
    {
        std::swap(buffer_, rhs.buffer_);
        std::swap(width_ , rhs.width_ );
        std::swap(height_, rhs.height_);
    }
private:
    int32_t width_;
    int32_t height_;
    Pixel*  buffer_;
};
Correct?
The copy constructor/assignment!
inline void swap(Surface& lhs, Surface& rhs) noexcept
Correct?
Yes, let's use it.
{
    lhs.swap(rhs);
}
struct GameObject Surface image; };
static void example_copy()
{
    Surface image{ 128256 };
    GameObject player{ image };
What happens?
32768 pixels get copied
}
Fixes?
struct GameObjectUptr std::unique_ptr<Surfaceimage; };
static void example_uptr()
{
    auto image = std::make_unique<Surface>(128256);
    GameObjectUptr player{ std::move(image) };
}
Problem?
1) Requires breaking change on API.
2) Extra allocation.
3) Every access to image requires extra indirection.
static void example_move()
{
    Surface image{ 128256 };
    GameObject player{ std::move(image) };
What happens?
32768 pixels get copied
but that can be changed (suspense)
}
static void example_move_assign()
{
    GameObject player;
    Surface image{ 128256 };
    player.image std::move(image);
}
2.6 (10)
Move Semantics

Move semantics

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
class Surface
{
public:
    Surface() noexcept :
        width_ },
        height_},
        buffer_nullptr }
    { }
    Surface(int32_t const width, int32_t const height)
    {
        if (width <= || height <= 0)
        {
            width_ height_ 0;
            buffer_ nullptr;
            return;
        }
        size_t const byte_count = width * height * sizeof(Pixel);
        width_  = width;
        height_ = height;
        buffer_ static_cast<Pixel*>(_aligned_malloc(byte_count, 16));
        if (buffer_ == nullptr)
        {
            throw std::bad_alloc{};
        }
        std::fill_n(buffer_width_ height_0);
    }
    Surface(Surface const& rhs) :
        Surface{ rhs.width_, rhs.height_ }
    {
        if (buffer_ != nullptr)
        {
            std::copy_n(rhs.buffer_width_ height_buffer_);
        }
    }
    Surfaceoperator=(Surface const& rhs)
    {
        auto copy = rhs;
        swap(copy);
        return *this;
    }
    Surface(Surface&& rhs) noexcept :
Move constructor, signature: T(T&&) noexcept
Important noexcept!
rvalue reference
        Surface{}
    {
*this === player.image (is empty)
rhs === local variable 'image' (correct data)
        swap(rhs);
*this === player.image (correct data)
rhs === local variable 'image' (is empty)
    }
    Surfaceoperator=(Surface&& rhs) noexcept
Move assignment, signature: T& operator=(T&&) noexcept
    {
        auto move = std::move(rhs);
Is std::move here necessary?
Absolutely! Without it, it would call the copy constructor!
        swap(move);
        return *this;
    }
    ~Surface() noexcept
    {
        if (buffer_ != nullptr)
        {
            _aligned_free(buffer_);
        }
    }
    void swap(Surface& rhs) noexcept
    {
        std::swap(buffer_, rhs.buffer_);
        std::swap(width_ , rhs.width_ );
        std::swap(height_, rhs.height_);
    }
private:
    int32_t width_;
    int32_t height_;
    Pixel*  buffer_;
};
inline void swap(Surface& lhs, Surface& rhs) noexcept
{
    lhs.swap(rhs);
}
struct GameObject Surface image; };
static void example_copy()
{
    Surface image{ 128256 };
    GameObject player{ image };
}
struct GameObjectUptr std::unique_ptr<Surfaceimage; };
static void example_uptr()
{
    auto image = std::make_unique<Surface>(128256);
    GameObjectUptr player{ std::move(image) };
}
static void example_move()
{
    Surface image{ 128256 };
    GameObject player{ std::move(image) };
What would you like to happen?
1) Player must have the correct image data.
2) No copying the image data.
3) 0 allocations, 0 frees.
4) 'image' should now be "empty".
How to make it happen?
}
static void example_move_assign()
{
    GameObject player;
    Surface image{ 128256 };
    player.image std::move(image);
What does this do?
Calls the copy assignment operator...
unless we implement a move assignment operator.
}
2.7 (11)
Move Semantics

Default operations

Info about default operations:

  • 6 default operations: default constructor, copy constructor, copy assignment, move constructor, move assignment, destructor.
  • Implicitly or explicit generated. (= default)
  • Default implementation applies it to each field.
  • noexcept if operation on all fields is noexcept.

Default implementation is similar but not identical to this:

123456789101112131415161718192021222324252627282930
class DefaultOperations
{
public:
    DefaultOperations() noexcept {} // No value initialization
    DefaultOperations(DefaultOperations const& rhs) :
        a{ rhs.},
        b{ rhs.}
    { }
    DefaultOperationsoperator=(DefaultOperations const& rhs)
    {
        a = rhs.a;
        b = rhs.b;
        return *this;
    }
    DefaultOperations(DefaultOperations&& rhs) noexcept :
        astd::move(rhs.a) },
        bstd::move(rhs.b) }
    { }
    DefaultOperationsoperator=(DefaultOperations&& rhs) noexcept
    {
        a std::move(rhs.a);
        b std::move(rhs.b);
        return *this;
    }
    ~DefaultOperations() noexcept {}
private:
    int a;
    std::vector<intb;
};

The default implementation may be trivial.[SO]

Initialization is bonkers.

2.8 (12)
Move Semantics

Summary

What we covered:

  • Non-copyable objects might be moveable.
  • Moving is generally faster.
  • Objects moved from should be in a valid state.
  • In standard library, state of moved from objects is unspecified.
  • For specific types (like std::unique_ptr), the moved-from state is specified.
  • You can implement your own move constructor and assignment.
  • Default implementation is applying that same operation to each field.
3.1 (13)
References

References

& and && <== these things

3.2 (14)
References

So what are & and &&?

  • T&& is an rvalue reference to T.
  • T&  is an lvalue reference to T.
  • References are just an alias to an object.
  • References to references don't exist.

The next question is, what are lvalues and rvalues?

These are known expression categories or value categories.

3.3 (15)
References

What are expression categories?

  • Category of an expression. What is an expression?
  • An expression is a sequence of operators and their operands, that specifies a computation. [C++ Ref]
  • Expressions include:
    • Any operator (including call, and member access).
    • Casts.
    • Some keywords (new, delete, sizeof, ...).
    • Names and constants.
  • Each expression is either an lvalue or rvalue.
  • There are 2 types of rvalues: xvalue and prvalue.
3.4 (16)
References

History of the terminology

Historically lvalue meant left value, rvalue meant right value.

123456789101112131415161718192021
int sum(int a, int b)
{
    a = a + b;
    return a;
}
int mul(int a, int b)
{
    return a * b;
}
static void example()
{
    int x;
    x = 5;
    7 = x;
What does this mean?
error C2106: '=': left operand must be l-value
x is an lvalue
5/7 is an rvalue
    int const y = sum(7, x);
Type of '7' === type of 'a' === int, the category is different.
    y = 5;
error C3892: 'y': you cannot assign to a variable that is const
    sum mul;
error C2659: '=': function as left operand
}

Left and right values are not, and never were accurate.

3.5 (17)
References

Distinguising expression categories

Rules of thumb: Any expression is an lvalue, if ...

  • ...you can take the address of it.
  • ...it has a name.
  • ...it has type T&.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
class Surface
{
public:
    Surface() noexcept :
        width_ },
        height_},
        buffer_nullptr }
    { }
    Surface(int32_t const width, int32_t const height)
    {
        if (width <= || height <= 0)
lvalue
lvalue
rvalue
rvalue
rvalue
        {
            width_ height_ 0;
lvalue
lvalue
            buffer_ nullptr;
lvalue
rvalue
            return;
        }
        size_t const byte_count = width * height * sizeof(Pixel);
        width_  = width;
        height_ = height;
        buffer_ static_cast<Pixel*>(_aligned_malloc(byte_count, 16));
        if (buffer_ == nullptr)
rvalue
        {
            throw std::bad_alloc{};
rvalue
Is throw even an expression?
Yes it is! Its type: void
rvalue
        }
        std::fill_n(buffer_width_ height_0);
lvalue
rvalue
    }
    Surface(Surface const& rhs) :
        Surface{ rhs.width_, rhs.height_ }
    {
        if (buffer_ != nullptr)
        {
            std::copy_n(rhs.buffer_width_ height_buffer_);
        }
    }
    Surfaceoperator=(Surface const& rhs)
    {
        auto copy = rhs;
        swap(copy);
        return *this;
    }
    Surface(Surface&& rhs) noexcept :
        Surface{}
    {
        swap(rhs);
    }
    Surfaceoperator=(Surface&& rhs) noexcept
    {
        auto move = std::move(rhs);
        swap(move);
        return *this;
    }
    ~Surface() noexcept
    {
        if (buffer_ != nullptr)
        {
            _aligned_free(buffer_);
        }
    }
    void swap(Surface& rhs) noexcept
    {
        std::swap(buffer_, rhs.buffer_);
        std::swap(width_ , rhs.width_ );
        std::swap(height_, rhs.height_);
    }
private:
    int32_t width_;
    int32_t height_;
    Pixel*  buffer_;
};
inline void swap(Surface& lhs, Surface& rhs) noexcept
{
    lhs.swap(rhs);
}
struct GameObject Surface image; };
static void example_copy()
{
    Surface image{ 128256 };
    GameObject player{ image };
}
struct GameObjectUptr std::unique_ptr<Surfaceimage; };
static void example_uptr()
{
    auto image = std::make_unique<Surface>(128256);
    GameObjectUptr player{ std::move(image) };
}
static void example_move()
{
    Surface image{ 128256 };
    GameObject player{ std::move(image) };
}
static void example_move_assign()
{
    GameObject player;
    Surface image{ 128256 };
    player.image std::move(image);
}
3.6 (18)
References

T&& rvalue references

A parameter or variable of type T&& can reference an rvalue.

1234567891011121314151617
void by_value     (int   x) { ++x; }
void by_lvalue_ref(int&  x) { ++x; }
void by_rvalue_ref(int&& x) { ++x; }
static void example()
{
    int number = 3;
    by_value     (number); // copies    ==> number == 3
Does what?
Copies number, still number == 3
    by_lvalue_ref(number); // reference ==> number == 4
Does what?
References number, now number == 4
    by_rvalue_ref(number); // error
Does what?
error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
    by_value     (int{7}); // copies    ==> temporary object == 7
Does what?
Copies temporary object, temporary object == 7
    by_lvalue_ref(int{7}); // error
Does what?
error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
    by_rvalue_ref(int{7}); // reference ==> temporary object == 8
Does what?
References temporary object, temporary object == 8
    by_rvalue_ref(std::move(number)); // reference ==> number == 5
References number, number == 5
What does std::move actually do?
Hint: There are 2 copies and 0 moves in this function.
std::move turns any expression into an rvalue
}

An rvalue reference is still a reference!

References alias objects.

3.7 (19)
References

Function overloading

1234567891011121314151617181920212223242526272829
inline void fn(T& ) { puts("l"); }
inline void fn(T&&) { puts("r"); }
static void example()
{
    T   val{};
    T&  lvr = val;
    T&& rvr = T{};
    fn(val); // l
    fn(lvr); // l
    fn(rvr); // l
    fn(T{}); // r
    puts("");
    fn(std::move(val)); // r
    fn(std::move(lvr)); // r
    fn(std::move(rvr)); // r
    fn(std::move(T{})); // r
    puts("");
    fn(static_cast<decltype(val)>(val));   // r, decltype(val) === T    Copies val!
    fn(static_cast<decltype(lvr)>(lvr));   // l, decltype(lvr) === T&
    fn(static_cast<decltype(rvr)>(rvr));   // r, decltype(rvr) === T&&
    fn(static_cast<decltype(T{})>(T{}));   // r, decltype(T{}) === T
    puts("");
    fn(static_cast<decltype(val)&&>(val)); // r, decltype(val) === T    decltype(val)&& === T&&
    fn(static_cast<decltype(lvr)&&>(lvr)); // l, decltype(lvr) === T&   decltype(lvr)&& === T&
    fn(static_cast<decltype(rvr)&&>(rvr)); // r, decltype(rvr) === T&&  decltype(rvr)&& === T&&
    fn(static_cast<decltype(T{})&&>(T{})); // r, decltype(T{}) === T    decltype(T{})&& === T&&
}
3.8 (20)
References

Function overloading (cont)

Summarizing

  • Reminder: A variable or parameter of T&& is still an lvalue.
  • Overloading between T& and T&& is done based on expression category.
  • References to references collapse.
    123456
    using int&;
    using int&&;
    using LL L&;  // int &  &  ==> int &
    using LR L&&; // int &  && ==> int &
    using RL R&;  // int && &  ==> int &
    using RR R&&; // int && && ==> int &&
  • decltype(name) gives the declared type of name.
  • Binding or casting an lvalue or xvalue to the non-reference type, will construct it.
  • But casting a prvalue to the non-reference type will not.
3.9 (21)
References

prvalue and xvalue

Commonly used xvalues come from:

  1. Casting to an rvalue reference:
    1
    static_cast<int&&>(5)
  2. Function call that returns an rvalue reference:
    12
    int&& test();
    test()

Most other rvalues are prvalues.[C++ Ref]


Question: When would you return an rvalue reference from a function?

When you want to return both a reference and an rvalue.


Reminder: An rvalue reference is still a reference!

1
int&& example() { return 5; } // BAD! Dangling reference
3.10 (22)
References

decltype returns

If decltype's argument is in parentheses, it also uses the expression category.

123456789101112131415161718192021222324
struct IntWrapper int value; };
float sum(int const a, float const& b) { return a + b; }
int   x = 5;
int&  y = x;
int&& z = 5;
decltype( x )                   // int
decltype((x))                   // int&
decltype)                   // int
decltype((5))                   // int
decltype( y )                   // int&
decltype((y))                   // int&
decltype( z )                   // int&&
decltype((z))                   // int&
decltypeIntWrapper{5}.value ) // int
decltype((IntWrapper{5}.value)) // int&&
decltypesum )                 // float   (int, float const&)
decltype((sum))                 // float(&)(int, float const&)
4.1 (23)
Perfect forwarding

Perfect forwarding

To wrap functions correctly.

4.2 (24)
Perfect forwarding

Creating components

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
class Component
{
public:
    virtual void update(float delta_seconds) = 0;
    virtual ~Component() noexcept default;
};
struct Particle
{
    float xy;
    float vxvy;
};
class ParticleSystem public Component
{
    void update(float const delta_seconds) override
    {
        for (auto& particle : particles)
        {
            particle.+= particle.vx * delta_seconds;
            particle.+= particle.vy * delta_seconds;
        }
    }
public:
    std::vector<Particleparticles;
};
class GameObject
{
public:
    template <typename T>
    Tcreate_component()
    {
        components_.push_back(std::make_unique<T>());
        return static_cast<T&>(*components_.back());
    }
    template <typename Ttypename P>
    Tcreate_component(param)
    {
        components_.push_back(std::make_unique<T>(std::move(param)));
        return static_cast<T&>(*components_.back());
    }
    template <typename Ttypename P>
    Tcreate_component(const& param)
    {
        components_.push_back(std::make_unique<T>(param));
What if we change it to std::move(param)?
Would be xvalue of type: P const&&
Cannot call: T(P&&). Can call: T(P const&). Copies value.
        return static_cast<T&>(*components_.back());
    }
    template <typename Ttypename P>
    Tcreate_component(P&& param)
ParticleSystem& param
ONLY with rvalue reference to deduced type, special name "forwarding reference".
Category of 'param'?
Category: lvalue, type: ParticleSystem&&.
    {
        components_.push_back(std::make_unique<T>(std::forward<P>(param)));
What should wrap 'param'?
Hint: param's type is either ParticleSystem& or ParticleSystem&&
static_cast<decltype(param)>(param) would work
Or use std::forward<P>.
        return static_cast<T&>(*components_.back());
    }
    template <typename Ttypename... P>
    Tcreate_component(P&&... params)
    {
        static_assert(std::is_base_of_v<ComponentT>, "T must be a Component");
        components_.push_back(std::make_unique<T>(std::forward<P>(params)...));
        return static_cast<T&>(*components_.back());
    }
private:
    std::vector<std::unique_ptr<Component>> components_;
};
void create_fire_particles(ParticleSystem& ps)
{
    ps.particles.clear();
    std::default_random_engine rnd;
    for (int i = 0; i < 100; i++)
    {
        ps.particles.push_back(
        {
            /*  x */std::uniform_real_distribution<float>{ -0.2f0.2f }(rnd),
            /*  y */std::uniform_real_distribution<float>{ -0.2f0.2f }(rnd),
            /* vx */std::uniform_real_distribution<float>{ -0.2f0.2f }(rnd),
            /* vy */std::uniform_real_distribution<float>{  2.0f8.0f }(rnd),
        });
    }
}
void example_basic()
{
    GameObject fire;
    auto& particle_system = fire.create_component<ParticleSystem>();
How to write create_component?
    create_fire_particles(particle_system);
Fills the particle system with data.
}
void example_multiple()
{
    ParticleSystem ps;
    create_fire_particles(ps);
    GameObject fire;
    fire.create_component<ParticleSystem>(ps);
What happens with ps? (Copies, moves, etc.)
1st copy, 2nd move (or copy if non-moveable).
Ideally always 1 copy.
What happens?
1st reference (no-op), 2nd copy.
Does this compile?
Yes! P === ParticleSystem& ==> P&& === ParticleSystem&
What happens?
1 copy, identical to calling ParticleSystem{ps}
    GameObject another_fire;
    another_fire.create_component<ParticleSystem>(std::move(ps));
What happens?
1st reference (no-op), 2nd copy.
What happens?
}
void example_error()
{
    GameObject fire;
    fire.create_component<int>(5);
example.cpp(62): error C2664: 'void std::vector<std::unique_ptr<Component,std::default_delete<_Ty>>,std::allocator<std::unique_ptr<_Ty,std::default_delete<_Ty>>>>::push_back(std::unique_ptr<_Ty,std::default_delete<_Ty>> &&)': cannot convert argument 1 from 'std::unique_ptr<T,std::default_delete<_Ty>>' to 'const std::unique_ptr<Component,std::default_delete<_Ty>> &'
example.cpp(61): error C2338: T must be a Component [with T=int]
}
4.3 (25)
Perfect forwarding

Creating components (summary)

Takeaways:

  • T&& is forwarding reference iif T is deduced type.
  • No cv-qualifiers allowed on forwarding reference!
  • If argument is an lvalue, the compiler adds lvalue reference to the deduced type.[C++ Ref]
  • Combine with std::forward<T>.
  • Universal reference is synonymous with forwarding reference.
  • Use static_assert to make your error messages more readable.
4.4 (26)
Perfect forwarding

Return values

12345678910111213141516171819202122232425262728
#define  IS_LVALUE(EXPR) ( std::is_lvalue_reference_v<decltype((EXPR))>)
#define  IS_XVALUE(EXPR) ( std::is_rvalue_reference_v<decltype((EXPR))>)
#define IS_PRVALUE(EXPR) (!std::is_reference_v       <decltype((EXPR))>)
template <typename Funtypename... P>
decltype(autopassthrough(Fun fn, P&&... v)
{
    return fn(std::forward<P>(v)...);
}
What return type?
int   idt_integer(int& v) { return v; }
int&  lvr_integer(int& v) { return v; }
int&& rvr_integer(int& v) { return static_cast<int&&>(v); }
static void example()
{
    int x{ };
    static_assert(IS_PRVALUEidt_integer(x) ));
    static_assertIS_LVALUElvr_integer(x) ));
    static_assertIS_XVALUErvr_integer(x) ));
    static_assertIS_XVALUEstd::move<int&>(x) ));
    static_assert(IS_PRVALUEpassthrough(idt_integer, x) ));
    static_assertIS_LVALUEpassthrough(lvr_integer, x) ));
    static_assertIS_XVALUEpassthrough(rvr_integer, x) ));
    static_assertIS_XVALUEpassthrough(std::move<int&>, x) ));
How to make IS_LVALUE, IS_XVALUE, IS_PRVALUE?
}

When is this useful? Rarely, but if you need it, it's invaluable.

4.5 (27)
Perfect forwarding

Performance

1234567891011121314
std::vector<intgenerate_numbers()
{
    return 01234};
}
std::vector<intpassthrough()
{
    return generate_numbers();
}
void example()
{
    auto numbers = passthrough();
}

How often does the vector get copied?

0 copies (and 0 moves).

Guaranteed by standard!

4.6 (28)
Perfect forwarding

Implementation of std::move and std::forward

123456789101112131415161718
template <typename T>
constexpr std::remove_reference_t<T>&& move(T&& v) noexcept
Forwarding reference
{
    return static_cast<std::remove_reference_t<T>&&>(v);
Why remove reference from T?
T& && would otherwise collapse to T&
}
template <typename T>
constexpr T&& forward(std::remove_reference_t<T>& v) noexcept
Called if argument is lvalue. (Almost always.)
{
    return static_cast<T&&>(v);
If T ==> T&&. If T& ==> T&. If T&& ==> T&&.
}
template <typename T>
constexpr T&& forward(std::remove_reference_t<T>&& v) noexcept
Called if argument is rvalue. (Almost never.)
{
    static_assert(!std::is_lvalue_reference_v<T>, "incorrect template parameter to T");
Value being forwarded is an rvalue. Cannot convert rvalue to lvalue.
    return static_cast<T&&>(v);
}

No compiler magic, just plain code.

4.7 (29)
Perfect forwarding

Summary

Summary:

  • Perfect forwarding to pass parameters/return values to/from other functions.
  • Be aware of forwarding references (T&& where T is deduced).
  • Return by value, it gets optimized.

Guidelines:

  • A move operation should leave its source in a valid state.[C.64]
  • Use std::move when you need to move an object to another scope.[ES.56]
  • Pass by T&& for parameters that consume the input.[F.18]
  • Only use forwarding references inside std::forward.[F.19]
5.1 (30)
Exercises

Exercises

Practice makes perfect.

5.2 (31)
Exercises

Expression Category quiz!

Goal: Look at an expression, and determine the expression category.


  • 15 questions.
  • Answers immediately visible after submitting.

Link: https://goo.gl/forms/bE1bXpzyU5TjiOCc2

Estimated duration: 2-5 minutes.

5.3 (32)
Exercises

Exercise 1 - Move operations

Goal: Write copy/move constructor/assignment for BitArray class.


Steps:

  • Add declarations in exercise.hpp at line 9.
  • Add definitions in exercise.cpp at line 36.
  • Make the 16 unit tests pass.

Uses TestAllocator::allocate   instead of new unsigned char[].

Uses TestAllocator::deallocate instead of delete[].


Knowledge: Default operations, exception safety.

Estimated duration: 5-60 minutes.

5.4 (33)
Exercises

Exercise 2 - Combined IO streams

Goal: Write a function that appends (<<) each of its arguments to a std::stringstream.


  • Implement the function in make_sstream.hpp.
  • Make the 6 unit tests pass.

Knowledge: Variadic templates, perfect forwarding.

Estimated duration: 5-20 minutes.

6.1 (34)

Thanks for attending

Questions?

6.2 (35)

Contact

Feel free to contact me if you have questions later on.

E-mail 143042@nhtv.nl
Slack Caspar143042
Discord Aidiakapi#2177

Please give me feedback! https://goo.gl/forms/zVXOH97o1iyB4qIj1