Session 7 Summary

Code Examples for Session 07

Excursions: Demoscene

Rockstar system programming: showcasing the swaggiest of reasons to learn C++

Demo Programming for Beginners

Polymorphism

Static Polymorphism (CRTP)

Dynamic Polymorphism (virtual)

Perfect Forwarding

The term “universal reference” is somewhat colloquial and has been deprecated in favor of the now-standard naming forwarding references

References

The following references explain the same principles, just in different ways and level of detail.

Talks:

  • Don’t help the compiler.
    A masterpiece, but requires a basic understanding of forwarding, reference categories and reference collapsing rules.

Secondary References

The Problem

We used rvalue references to implement move semantics (move-construction and move-assignments) in classes. Any function can be overloaded for rvalue references:

void foo(X&  x); // lvalue reference overload
void foo(X&& x); // rvalue reference overload

X x;
X foobar() { return X { }; }

foo(x);        // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

Essentially, rvalue references allow to select a function overload at compile time depending on whether it is used as lvalue or rvalue in an expression.

With overloads for rvalue references, we would need at least two variants of a function taking a single argument to achieve “ideal” avoidance of temporary copies.

With two arguments, this is already getting awkward:

void bar(T &  a, T &  b);
void bar(T && a, T &  b);
void bar(T &  a, T && b);
void bar(T && a, T && b);

You see where this is going.

The Solution

First, we have to recognize that the following Reference Collapsing Rules exist since C++11:

A&  &  -> A&
A&  && -> A&
A&& &  -> A&
A&& && -> A&&

Secondly, there is a template argument deduction rule for function templates that take an argument by rvalue reference to a template argument:

template<typename T>
void foo(T &&);

The following rules apply:

  • When foo is called on an lvalue of type A:
    \(\Rightarrow\) T resolves to A&
    \(\Rightarrow\) reference collapsed: A&
  • When foo is called on an rvalue of type A:
    \(\Rightarrow\) T resolves to A
    \(\Rightarrow\) A&&

Forwarding Reference

In lab session 5, we learned declarations of rvalue references (actually: xvalue):

void wrapper(Foo && foo) {
   func(foo);
}

To tell whether you need std::move or not, just remember that it is equivalent to static_cast<Foo &&>(foo). In this example, foo already is an rvalue reference so there is no need to std::move. It would not hurt but just have no effect.

Now I need you to relax and remember that high blood pressure leads to serious disease, so try not to get upset.

In the following example, parameters rval_ref and fwd_ref do not have the same type category:

void wrapper_rval(Foo && rval_ref);

template <class T>
void wrapper_fwdr(T && fwd_ref);

In wrapper_univ, the parameter is a forwarding reference

Sometimes called universal reference in dated references).
Please note that the term universal reference has been coined by Scott Meyers and is not official terminology.
In fact, the term universal reference is wrong and misleading, see ISO C++ N4164

The correct definition:

Forwarding Reference

rvalue reference in a type deducing context

Idiom: Function Template Wrappers

std::pair<int, int> one;
std::pair<int, int> two;

one = std::make_pair(10, 20);
two = std::make_pair(10.5, 'A'); // implicit conversion from pair<double,char>
#include <utility>

template<typename T, typename U>
std::pair<T, U> make_pair_wrapper(T && t, U && u) {
  return std::make_pair(std::forward<T>(t),
                        std::forward<U>(u));
}

int  a = 0;
auto p = make_pair_wrapper(a, 3);

Type Deduction: auto and decltype

For examples of auto type deduction in declarations, see this listing.

Example from Effective Modern C++:

// C++11
template<typename Container, typename Index> // works, but
auto Access(Container& c, Index i)           // requires
  -> decltype(c[i])                          // refinement
{
  // ...
  return c[i];
}

// C++14
template<typename Container, typename Index> // C++14;
auto Access(Container& c, Index i)           // not quite
{                                            // correct
  // ...
  return c[i];  // return type deduced from c[i]
}

Using decltype(auto):

// Perfect forwarding C++11 version
template<typename Container, typename Index>
auto
authAndAccess(Container&& c, Index i)
  -> decltype(std::forward<Container>(c)[i])
{
  authenticateUser();
  return std::forward<Container>(c)[i];
}
// Perfect forwarding C++14 version
template<typename Container, typename Index>
decltype(auto)
authAndAccess(Container&& c, Index i)
{
  authenticateUser();
  return std::forward<Container>(c)[i];
}

Guideline: “almost always auto” (aaa)

Using auto when you supposedly know the eventual type of a variable might appear sloppy and lazy to you. But quite the opposite is the case: using auto by convention is actually considered a good practice.

Let’s discuss the following declarations:

Widget w_dc_1;
Widget w_dc_2 = Widget();

Widget w_il_1 = { 2 };
Widget w_il_2   { 2 };

auto   w_a_1  = Widget();
auto   w_a_2  = Widget(4);