๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

C++ Perfect Forwarding

Perfect forwarding hands an argument onward without changing its value category. A named value is copied onward, and a temporary is moved onward.

A factory that always copies

The factory takes its argument by const reference. A temporary argument is copied as well, and the printed lines prove it.

#include <iostream>
#include <string>
#include <utility>

class Label {
public:
  Label(const std::string& text) : text_(text) {
    std::cout << "Copied in\n";
  }
  Label(std::string&& text) : text_(std::move(text)) {
    std::cout << "Moved in\n";
  }
  const std::string& text() const {
    return text_;
  }

private:
  std::string text_;
};

Label makeLabelCopy(const std::string& value) {
  return Label(value);
}

int main() {
  std::string name = "hello";
  Label first = makeLabelCopy(name);
  Label second = makeLabelCopy(std::string("world"));
  std::cout << "First: " << first.text() << "\n";
  std::cout << "Second: " << second.text() << "\n";
  return 0;
}

A factory that forwards

The forwarding factory keeps the category of each argument. The named value copies in, and the temporary moves in.

#include <iostream>
#include <string>
#include <utility>

class Label {
public:
  Label(const std::string& text) : text_(text) {
    std::cout << "Copied in\n";
  }
  Label(std::string&& text) : text_(std::move(text)) {
    std::cout << "Moved in\n";
  }
  const std::string& text() const {
    return text_;
  }

private:
  std::string text_;
};

template <typename T>
Label makeLabel(T&& value) {
  return Label(std::forward<T>(value));
}

int main() {
  std::string name = "hello";
  Label first = makeLabel(name);
  Label second = makeLabel(std::string("world"));
  std::cout << "First: " << first.text() << "\n";
  std::cout << "Second: " << second.text() << "\n";
  return 0;
}

A forwarding constructor

A constructor can forward in the same way. The Wrapper constructor passes its argument into the Label member it holds.

#include <iostream>
#include <string>
#include <utility>

class Label {
public:
  Label(const std::string& text) : text_(text) {
    std::cout << "Copied in\n";
  }
  Label(std::string&& text) : text_(std::move(text)) {
    std::cout << "Moved in\n";
  }
  const std::string& text() const {
    return text_;
  }

private:
  std::string text_;
};

class Wrapper {
public:
  template <typename T>
  Wrapper(T&& value) : label_(std::forward<T>(value)) {}
  const std::string& text() const {
    return label_.text();
  }

private:
  Label label_;
};

int main() {
  std::string name = "alpha";
  Wrapper first(name);
  Wrapper second(std::string("beta"));
  std::cout << "First: " << first.text() << "\n";
  std::cout << "Second: " << second.text() << "\n";
  return 0;
}

Example

#include <iostream>
#include <string>
#include <utility>

class Label {
public:
  Label(const std::string& text) : text_(text) {
    std::cout << "Copied in\n";
  }
  Label(std::string&& text) : text_(std::move(text)) {
    std::cout << "Moved in\n";
  }
  const std::string& text() const {
    return text_;
  }

private:
  std::string text_;
};

class Duo {
public:
  Duo(Label first, Label second) : first_(std::move(first)), second_(std::move(second)) {}
  void print() const {
    std::cout << "Pair: " << first_.text() << " and " << second_.text() << "\n";
  }

private:
  Label first_;
  Label second_;
};

template <typename A, typename B>
Duo makePair(A&& first, B&& second) {
  Label one(std::forward<A>(first));
  Label two(std::forward<B>(second));
  return Duo(std::move(one), std::move(two));
}

int main() {
  std::string left = "red";
  Duo pair = makePair(left, std::string("blue"));
  pair.print();
  return 0;
}

Key points

  • A value with a name is called an lvalue.
  • A value without a name, such as a temporary, is called an rvalue.
  • A template parameter written as T&& is a forwarding reference that keeps the category of its argument.
  • Inside the function the parameter has a name, so it looks like an lvalue on its own.
  • std::forward restores the original category when the argument is passed on.
  • Forwarding lets one factory copy lvalues and move rvalues with no duplicated code.

In this example, makePair forwards two arguments into a Duo. The named string copies into the first Label, and the temporary string moves into the second. The pair then prints both texts on one line.

Note

std::forward is written with the template type in angle brackets, as in std::forward<T>(value).

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What does std::forward do inside a forwarding function?