Day: June 25, 2020
Rvalue References
Understand std::move
Because std::move does nothing but cast its argument to an rvalue, there have been
suggestions that a better name for it might have been something like rvalue_cast.
//c++11
template<typename T> // in namespace std
typename remove_reference<T>::type&&
move(T&& param)
{
using ReturnType = // alias declaration;
typename remove_reference<T>::type&&;
return static_cast<ReturnType>(param);
}
// C++14; still in
template<typename T>
decltype(auto) move(T&& param) // namespace std
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
Distinguish universal references from rvalue
references.
• If a function template parameter has type T&& for a deduced type T, or if an object is declared using auto&&, the parameter or object is a universal reference.
• If the form of the type declaration isn’t precisely type&&, or if type deduction does not occur, type&& denotes an rvalue reference.
• Universal references correspond to rvalue references if they’re initialized with rvalues. They correspond to lvalue references if they’re initialized with lvalues.
void f(Widget&& param); // rvalue reference
Widget&& var1 = Widget(); // rvalue reference
auto&& var2 = var1; // is a universal reference
template<typename T>
void f(std::vector<T>&& param); // rvalue reference
template<typename T>
void f(T&& param); // is a universal reference
template<typename T>
void f(std::vector<T>&& param); // param is an rvalue reference
std::vector<int> v;
f(v); // error! can't bind lvalue to
// rvalue reference
auto timeFuncInvocation =
[](auto&& func, auto&&... params) // C++14
{
start timer;
std::forward<decltype(func)>(func)( // invoke func
std::forward<decltype(params)>(params)... // on params
);
stop timer and record elapsed time;
};
Use std::move on rvalue references,
std::forward on universal references.
• Apply std::move to rvalue references and std::forward to universal refer‐
ences the last time each is used.
• Do the same thing for rvalue references and universal references being
returned from functions that return by value.
• Never apply std::move or std::forward to local objects if they would other‐
wise be eligible for the return value optimization.
class Widget {
public:
Widget(Widget&& rhs) // rhs is rvalue reference
: name(std::move(rhs.name)),
p(std::move(rhs.p))
{ … }
…
private:
std::string name;
std::shared_ptr<SomeDataStructure> p;
};
class Widget {
public:
template<typename T>
void setName(T&& newName) // newName is
{ name = std::forward<T>(newName); } // universal reference
…
};
using std::move with universal references,that can have the
effect of unexpectedly modifying lvalues
Matrix // by-value return
operator+(Matrix&& lhs, const Matrix& rhs)
{
lhs += rhs;
return std::move(lhs); // move lhs into
}
Matrix // as above
operator+(Matrix&& lhs, const Matrix& rhs)
{
lhs += rhs;
return lhs; // copy lhs into
}
template<typename T>
Fraction // by-value return
reduceAndCopy(T&& frac) // universal reference param
{
frac.reduce();
return std::forward<T>(frac); // move rvalue into return
}
don’t move local variable into return value
Widget makeWidget() // Moving version of makeWidget
{
Widget w;
…
return std::move(w); // move w into return value
}
Avoid overloading on universal references
std::multiset<std::string> names; // global data structure
void logAndAdd(const std::string& name)
{
auto now = // get current time
std::chrono::system_clock::now();
log(now, "logAndAdd"); // make log entry
names.emplace(name); // add name to global data
}
std::string petName("Darla");
logAndAdd(petName); // pass lvalue std::string
logAndAdd(std::string("Persephone")); // pass rvalue std::string
logAndAdd("Patty Dog"); // pass string literal
in the previous example in second and third call name itself is an lvalue, so it’s copied into names.
template<typename T>
void logAndAdd(T&& name)
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(std::forward<T>(name));
}
std::string petName("Darla"); // as before
logAndAdd(petName); // as before, copy lvalue into multiset
logAndAdd(std::string("Persephone")); // move rvalue instead of copying it
logAndAdd("Patty Dog"); // create std::string in multiset instead of copying a temporary std::string
Constraining templates that take universal references
Alternatives to the combination of universal references and overloading
include the use of distinct function names, passing parameters by lvaluereference-to-const, passing parameters by value, and using tag dispatch.
• Constraining templates via std::enable_if permits the use of universal references and overloading together, but it controls the conditions under which compilers may use the universal reference overloads.
• Universal reference parameters often have efficiency advantages, but they typically have usability disadvantages.
class Person {
public:
template<
typename T,
typename = typename std::enable_if<!std::is_base_of<Person,typename std::decay<T>::type>::value>::type>
explicit Person(T&& n);
…
};
//c++14
class Person {
public:
template<
typename T,
typename = std::enable_if_t<!std::is_base_of<Person,std::decay_t<T>>::value>>
explicit Person(T&& n);
…
};
#include <type_traits>
template<typename T>
class YourClass {
YourClass() {
// Compile-time check
static_assert(std::is_base_of<BaseClass, T>::value, "type parameter of this class must derive from BaseClass");
// ...
}
}
class Person {
public:
template<typename T,typename = std::enable_if_t<std::is_base_of_v<Person,std::decay_t<T>>>>
explicit Person(T&& n){
}
Person()= default;
};
int main(int argc,char* argv[]){
Person pp;
Person p(pp);
return 0;
}