B2B Forwarding References
B2B Forwarding References
• Facilitate discussion
• Describe
– pitfalls
– corner cases
• Provide recommendations
• Lot's of coding
“GENERALIZED” LVALUES
or function
“GENERALIZED” LVALUES
or function
"PURE" RVALUES
“GENERALIZED” LVALUES
or function
"PURE" RVALUES
"EXPIRING" LVALUES
LVALUE
LVALUE
RVALUE
prvalue or an xvalue
https://ahaslides.com/FWDREF
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // ???
f(ci); // ???
f(std::move(i)); // ???
f(std::move(ci)); // ???
f(42); // ???
f(make_int()); // ???
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // ERROR
f(ci); // ERROR
f(std::move(i)); // ERROR
f(std::move(ci)); // ERROR
f(42); // ERROR
f(make_int()); // ERROR
address.
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(&i); // ???
f(&ci) // ???
f(&std::move(i)); // ???
f(&std::move(ci)); // ???
f(&42); // ???
f(&make_int()); // ???
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(&i); // OK
f(&ci) // ERROR
f(&std::move(i)); // ERROR
f(&std::move(ci)); // ERROR
f(&42); // ERROR
f(&make_int()); // ERROR
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(&i); // ???
f(&ci) // ???
f(&std::move(i)); // ???
f(&std::move(ci)); // ???
f(&42); // ???
f(&make_int()); // ???
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(&i); // OK
f(&ci) // OK
f(&std::move(i)); // ERROR
f(&std::move(ci)); // ERROR
f(&42); // ERROR
f(&make_int()); // ERROR
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // ???
f(ci); // ???
f(std::move(i)); // ???
f(std::move(ci)); // ???
f(42); // ???
f(make_int()); // ???
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // OK
f(ci); // ERROR
f(std::move(i)); // ERROR
f(std::move(ci)); // ERROR
f(42); // ERROR
f(make_int()); // ERROR
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // ???
f(ci); // ???
f(std::move(i)); // ???
f(std::move(ci)); // ???
f(42); // ???
f(make_int()); // ???
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // OK
f(ci); // OK
f(std::move(i)); // OK
f(std::move(ci)); // OK
f(42); // OK
f(make_int()); // OK
POINTER REFERENCE
Use references when you can, and pointers when you have to.
-- C ++ FAQ
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // ???
f(ci); // ???
f(std::move(i)); // ???
f(std::move(ci)); // ???
f(42); // ???
f(make_int()); // ???
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // ERROR
f(ci); // ERROR
f(std::move(i)); // OK
f(std::move(ci)); // ERROR
f(42); // OK
f(make_int()); // OK
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // ???
f(ci); // ???
f(std::move(i)); // ???
f(std::move(ci)); // ???
f(42); // ???
f(make_int()); // ???
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // ERROR
f(ci); // ERROR
f(std::move(i)); // OK
f(std::move(ci)); // OK
f(42); // OK
f(make_int()); // OK
foo(make_int());
foo(make_int()); foo(make_int());
foo(make_int()); foo(make_int());
void foo(int&& x)
{
boo(x); // ERROR
}
All move semantics related logic is provided by the function that is being
• Deduced function template parameter declared as rvalue reference to cv-unqualified type template
parameter
• Deduced function template parameter declared as rvalue reference to cv-unqualified type template
parameter
int i = 42;
const int ci = 42;
int make_int() { return 42; }
f(i); // OK
f(ci); // OK
f(std::move(i)); // OK
f(std::move(ci)); // OK
f(42); // OK
f(make_int()); // OK
template<class T>
int g(const T&& y);
template<class T>
int g(const T&& y);
template<class T>
struct A {
template<class U, class Z = T>
A(T&& t, U&& u, Z&& z); // only 'u' and 'z' are forwarding references
};
template<class T>
int g(const T&& y);
template<class T>
struct A {
template<class U, class Z = T>
A(T&& t, U&& u, Z&& z); // only 'u' and 'z' are forwarding references
};
template<typename T>
void foo(std::vector<T>&& v);
template<class T>
int g(const T&& y);
template<class T>
struct A {
template<class U, class Z = T>
A(T&& t, U&& u, Z&& z); // only 'u' and 'z' are forwarding references
};
template<typename T>
void foo(std::vector<T>&& v);
int i = 42;
const int ci = 42;
int make_int() { return 42; }
template<typename T>
void f(T&& x);
f(i); // f<int&>(int&)
f(ci); // f<const int&>(const int&)
f(std::move(i)); // f<int>(int&&)
f(std::move(ci)); // f<const int>(const int&&)
f(42); // f<int>(int&&)
f(make_int()); // f<int>(int&&)
int i = 42;
const int ci = 42;
int make_int() { return 42; }
template<typename T>
void f(T&& x);
f(i); // f<int&>(int&)
f(ci); // f<const int&>(const int&)
f(std::move(i)); // f<int>(int&&)
f(std::move(ci)); // f<const int>(const int&&)
f(42); // f<int>(int&&)
f(make_int()); // f<int>(int&&)
int i = 42;
const int ci = 42;
int make_int() { return 42; }
int i = 42;
const int ci = 42;
int make_int() { return 42; }
ARGUMENT BINDS TO int* const int* int& const int& int&& const int&& T&&
"just" needs to read data, you probably should choose const lvalue
reference as an argument.
std::forward.
std::forward.
std::forward.
std::forward.
template<typename T>
[[nodiscard]] constexpr T&& forward(typename std::type_identity_t<T>& param)
{
return static_cast<std::type_identity_t<T>&&>(param);
}
template<typename T>
[[nodiscard]] constexpr T&& forward(typename std::type_identity_t<T>& param)
{
return static_cast<std::type_identity_t<T>&&>(param);
}
REFERENCE COLLAPSING
category.
class MyClass {
std::string txt_;
public:
explicit MyClass(const std::string& txt):
txt_(txt)
{}
explicit MyClass(std::string&& txt):
txt_(std::move(txt))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
MyClass(const std::string& txt1, const std::string& txt2):
txt1_(txt1), txt1_(txt1)
{}
MyClass(std::string&& txt1, std::string&& txt2):
txt1_(std::move(txt1)), txt2_(std::move(txt2))
{}
MyClass(const std::string& txt1, std::string&& txt2):
txt1_(txt1), txt1_(std::move(txt1))
{}
MyClass(std::string&& txt1, const std::string& txt2):
txt1_(std::move(txt1)), txt2_(txt2)
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
MyClass(std::string txt1, std::string txt2):
txt1_(std::move(txt1)), txt2_(std::move(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
MyClass(std::string txt1, std::string txt2):
txt1_(std::move(txt1)), txt2_(std::move(txt2))
{}
};
• Adds one move construction and destruction of a moved from object operations for lvalues and
xvalues
class MyClass {
std::string txt1_;
std::string txt2_;
public:
MyClass(std::string txt1, std::string txt2):
txt1_(std::move(txt1)), txt2_(std::move(txt2))
{}
};
• Adds one move construction and destruction of a moved from object operations for lvalues and
xvalues
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<typename T, typename U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<typename T, typename U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<typename T, typename U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
😢
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<std::same_as<std::string> T, std::same_as<std::string> U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<std::same_as<std::string> T, std::same_as<std::string> U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<std::same_as<std::string> T, std::same_as<std::string> U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<std::same_as<std::string> T, std::same_as<std::string> U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<typename T, typename U>
requires std::same_as<std::remove_cvref_t<T>, std::string> &&
std::same_as<std::remove_cvref_t<U>, std::string>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<typename T, typename U>
requires std::same_as<std::remove_cvref_t<T>, std::string> &&
std::same_as<std::remove_cvref_t<U>, std::string>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<typename T, typename U>
requires std::same_as<std::remove_cvref_t<T>, std::string> &&
std::same_as<std::remove_cvref_t<U>, std::string>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<std::convertible_to<std::string> T, std::convertible_to<std::string> U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<std::convertible_to<std::string> T, std::convertible_to<std::string> U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class MyClass {
std::string txt1_;
std::string txt2_;
public:
template<std::convertible_to<std::string> T, std::convertible_to<std::string> U>
MyClass(T&& txt1, U&& txt2):
txt1_(std::forward<T>(txt1)), txt2_(std::forward<U>(txt2))
{}
};
class int_or_empty {
int value_ = 0;
bool empty_ = true;
public:
int_or_empty() = default;
template<std::convertible_to<int> T>
constexpr int_or_empty(T&& v) :
value_{std::forward<T>(v)}, empty_{false}
{
}
constexpr bool empty() const
{ return empty_; }
template<std::convertible_to<int> T>
constexpr int_or_empty(T&& v) :
value_{std::forward<T>(v)}, empty_{false}
{
}
constexpr bool empty() const
{ return empty_; }
class int_or_empty {
int value_ = 0;
bool empty_ = true;
public:
int_or_empty() = default;
template<std::convertible_to<int> T>
requires (!std::same_as<std::remove_cvref_t<T>, int_or_empty>)
constexpr int_or_empty(T&& v) :
value_{std::forward<T>(v)}, empty_{false}
{
}
constexpr bool empty() const
{ return empty_; }
constexpr operator int() const
{ return value_; }
};
template<typename T>
void foo(T&&);
template<typename T>
void foo(T&&);
template<typename T>
requires (!std::is_lvalue_reference_v<T>)
void foo(T&&);
template<typename T>
requires (!std::is_lvalue_reference_v<T>)
void foo(T&&);
template<typename T>
task<void> foo(T&& t) { /* ... */ }
template<typename U>
task<void> boo(U&& u)
{
return foo(std::forward<U>(u));
}
int i = 123;
co_await boo(i); // T -> int&; t -> int&
co_await boo(42); // T -> int; t -> int&&
template<typename T>
auto wrapper(T&& v)
{
return foo(std::forward<T>(v));
}
template<typename T>
auto wrapper(T&& v)
{
return foo(std::forward<T>(v));
}
template<typename T>
auto&& wrapper(T&& v)
{
return foo(std::forward<T>(v));
}
template<typename T>
auto&& wrapper(T&& v)
{
return foo(std::forward<T>(v));
}
template<typename T>
decltype(auto) wrapper(T&& v)
{
return foo(std::forward<T>(v));
}
template<typename T>
decltype(auto) wrapper(T&& v)
{
return foo(std::forward<T>(v));
}
– reference by reference
template<typename T>
decltype(auto) wrapper(T&& v)
{
auto&& ret = foo1(std::forward<T>(v));
// ...
return foo2(std::forward<decltype(ret)>(ret));
}
template<typename T>
decltype(auto) wrapper(T&& v)
{
auto&& ret = foo1(std::forward<T>(v));
// ...
return foo2(std::forward<decltype(ret)>(ret));
}
for(auto&& x : f()) {
// ...
}
for(auto&& x : f()) {
// ...
}
Usage of auto&& is the safest way to use range for loops in a generic context
where we do not know which value category will be returned from a function
• Use both const MyType& and MyType&& for sinks where it is not too expensive
– consider MyType for expensive scenarios
• Use both const MyType& and MyType&& for sinks where it is not too expensive
– consider MyType for expensive scenarios
• Use T&& to forward the argument
– beware of single-argument constructors
– remember that it binds to everything not just the type you need
• Use both const MyType& and MyType&& for sinks where it is not too expensive
– consider MyType for expensive scenarios
• Use T&& to forward the argument
– beware of single-argument constructors
– remember that it binds to everything not just the type you need
• Use decltype(auto) as a function return type to perfectly return the result of another function's
invocation
• Use both const MyType& and MyType&& for sinks where it is not too expensive
– consider MyType for expensive scenarios
• Use T&& to forward the argument
– beware of single-argument constructors
– remember that it binds to everything not just the type you need
• Use decltype(auto) as a function return type to perfectly return the result of another function's
invocation