C++11 feels like a new language. Compared to its previous standards, C++11 packs more language features and libraries designed to make C++ programs easier to understand and faster. As the community is building up experience with the new features, new stylistic ways of using them are emerging. These styles (a.k.a. idioms) give the new language its unique flavor. This talk will present emerging idioms of using rvalue references -- a marquee feature of C++11 as many renowned experts call it. You will see how C++11 opens new possibilities to design class interfaces. Finally, you will learn some advanced use-cases of rvalue references which will likely make you feel something amiss in this flagship feature of C++11.
3. This talk is about programming language idioms
Definitions taken from Answers.com and Google.com
4. » Idioms
˃ Specific grammatical, syntactic, structural
expressions in a given programming language
to solve an implementation problem
˃ Often have names—create vocabulary like
patterns
˃ Repeat themselves
˃ Often cannot be modularized as libraries
˃ May get promoted as first-class features as a
language evolves
» Idioms vs. Patterns
˃ Idioms are patterns specific to a language
˃ Patterns are language independent
5. » Idioms of Using C++11 Rvalue References
» C++ Truths
˃ Rvalue References In Constructor: When Less Is More
˃ Perfect Forwarding of Parameter Groups in C++11
» A Sense of Design – Boris Kolpackov
˃ Efficient Argument Passing in C++11 (Parts 1, 2, and 3) (with permission)
6.
7. class Book {
// ....
private:
std::string _title;
std::vector<std::string> _authors;
std::string _publisher;
size_t _pub_year;
};
8. class Book {
public:
Book(const std::string & title,
const std::vector<std::string> & authors, Good old C++03
const std::string & pub, constructor
const size_t & pub_year);
// ....
private:
std::string _title;
std::vector<std::string> _authors;
std::string _publisher;
size_t _pub_year;
};
» Other alternatives
˃ An iterator-pair instead of const std::vector<std::string>&
˃ but lets keep it simple
9. class Book {
public:
Book(const std::string & title,
const std::vector<std::string> & authors, Constructor
const std::string & pub, parameters
const size_t & pub_year)
: _title(title),
_authors(authors),
_publisher(pub), make copies in *this
_pub_year(pub_year)
{}
private:
std::string _title;
std::vector<std::string> _authors;
std::string _publisher;
size_t _pub_year;
};
Is this class move enabled?
10. class Book {
public:
Book(const std::string & title,
const std::vector<std::string> & authors, No need to
const std::string & pub, write these
const size_t & pub_year); declarations
Book (const Book &) = default; // Copy Constructor
Book & operator = (const Book &) = default; // Copy Assign
Book (Book &&) = default; // Move Constructor
Book & operator = (Book &&) = default; // Move Assign
~Book() = default; // Destructor
private:
std::string _title; Compiler will
std::vector<std::string> _authors; provide them
std::string _publisher; where
size_t _pub_year; appropriate
};
12. class Book {
public:
Book(const std::string & title,
const std::vector<std::string> & authors,
const std::string & pub,
const size_t & pub_year); // Old c-tor
Book(std::string && title,
std::vector<std::string> && authors,
std::string && pub,
size_t && pub_year) Constructor with C++11
: _title(std::move(title)), rvalue references
_authors(std::move(authors)),
_publisher(std::move(publisher),
_pub_year(std::move(pub_year))
{}
// ... Members not shown
}; Is it now optimally move enabled?
13. class Book {
public:
Book(const std::string & title,
const std::vector<std::string> & authors,
const std::string & pub,
const size_t & pub_year); // Old constructor
Book(std::string && title,
std::vector<std::string> && authors,
std::string && pub,
size_t && pub_year) // New constructor
: _title(std::move(title)),
_authors(std::move(authors)),
_publisher(std::move(publisher),
_pub_year(std::move(pub_year))
{}
};
int main(void)
{
std::vector<std::string> authors { "A", "B", "C" }; Calls old
Book b1("Book1", authors, "O’Reilly", 2012); constructor!
const size_t year = 2012; Calls old
Book b2("Book1", { "Author" }, "O’Reilly", year);
constructor!
Book b3("Book", { "Author" }, "O’Reilly", 2012); // Calls New Ctor
}
14. Book::Book(std::string && title,
std::vector<std::string> && authors,
std::string && pub,
size_t && pub_year); // New constructor
lvalue
int main(void)
{
std::vector<std::string> authors { "A", "B", "C" };
Book b1("Book1", authors, "O’Reilly", 2012); lvalue
const size_t year = 2012;
Book b2("Book1", { "Author" }, "O’Reilly", year);
No lvalue
Book b3("Book", { "Author" }, "O’Reilly", 2012);
}
» Even one incompatible parameter (lvalue) will cause the
compiler to reject the new rvalue-only constructor
15. » Fortunately an optimal solution exists!
» But has a small problem 8 constructors!
˃ Implementations of all the constructors are different!
˃ Each Rvalue reference object must be std::moved
˃ In general exponential number of constructors
title authors pub year
string && vector<string> && string && size_t
string && vector<string> && const string & size_t
string && const vector<string> & string && size_t
string && const vector<string> & const string & size_t
const string & vector<string> && string && size_t
const string & vector<string> && const string & size_t
const string & const vector<string> & string && size_t
const string & const vector<string> & const string & size_t
16. » Help the compiler take the best decision
˃ Pass-by-value!
˃ std::move each parameter
˃ Compiler makes no more copies than absolutely necessary
˃ Copy-elision may avoid moves too!
class Book {
public:
Book(std::string title,
std::vector<std::string> authors,
std::string pub,
size_t pub_year)
Only one constructor
: _title (std::move(title)),
_authors (std::move(authors)),
_publisher(std::move(pub)),
_pub_year (std::move(pub_year))
{}
};
18. » Move is not free!
˃ Potentially 5 to 15 % performance degradation
˃ Benchmark!
» Many types don’t have efficient move operations
˃ std::array, std::complex, etc.
˃ Many C++03 libraries have not caught up with C++11
+ Compiler will not provide implicit move operations (in most cases)
» Move may be same as copy!
˃ Move construction Use move-ctor if available, otherwise use copy-
ctor!!
˃ Move==Copy when strings are small (small string optimization)
» Works best only when you know you are going to make a
copy
20. matrix operator+ (const matrix& x, const matrix& y)
{
matrix r (x);
r += y;
return r;
}
» matrix is movable (efficiently)
» matrix is copyable too
» operator + makes a copy
˃ Lets try to save it if we can!
21. matrix operator+ (matrix&& x, const matrix& y)
{
matrix r (std::move(x));
r += y; Look ma!
return r; No Copy!
}
matrix a, b;
matrix c = a * 2 + b;
Works great!
matrix d = a + b * 2;
Oops!
22. matrix operator+ (const matrix& x, const matrix& y) {
matrix r (x);
r += y; ARE YOU
return r; KIDDING
} ME?
matrix operator+ (const matrix& x, matrix&& y) {
matrix r (std::move(y));
r += x;
return r;
}
matrix operator+ (matrix&& x, const matrix& y) {
matrix r (std::move(x));
r += y;
return r;
}
matrix operator+ (matrix&& x, matrix&& y) {
matrix r (std::move(x));
r += y;
return r;
}
In general, exponential number of implementations
23. » Pass-by-value!
matrix operator+ (matrix x, matrix y) {
matrix r (std::move(x));
r += y;
return r;
}
matrix a, b;
matrix c = a*2 + b*4; Works great!
matrix d = a + b*4;
Two Unnecessary Moves
matrix e = a*2 + b;
matrix f = a + b; One Unnecessary Copy and Move
We need a way to detect lvalue/rvalue at runtime
24.
25. struct X { ... };
» Pass original type to a
void g(X&& t); // A forwarded function
void g(X& t); // B ˃ Forward lvalues as lvalues
template<typename T> ˃ Forward const lvalues as const lvalues
void f(T&& t) ˃ Forward rvalues as rvalues
{
g(std::forward<T>(t)); » Works very well with factory
} functions
int main() template <class Arg>
{ Blob * createBlob(Arg&& arg)
X x; {
f(x); // Calls B return new Blob(std::forward<Arg>(arg));
f(X()); // Calls A }
}
26. » Perfect forwarding can be made to work … But
» First, you must use a template
» Second, you need two template parameters (T and U)
» Then you must restrict them to the specific type you
are interested in (i.e., matrix)
˃ Most likely using enable_if
» Finally, you need to ask the right question
˃ IS_RVALUE(T, x)?
#define IS_RVALUE(TYPE, VAR)
(std::is_rvalue_reference<decltype(std::forward<TYPE>((VAR)))>::value)
(!std::is_reference<TYPE>::value)
In short, It is too much noise!
27. Parameter Example Best Case Sub-optimal Case
Const f(const matix &); Function makes Function makes a copy and
Reference no copy and pass pass rvalue
lvalue (missed optimization)
Pass-by-value f(matrix); Function makes a Function makes no copy
copy and pass and pass lvalue
rvalue (unnecessary copy)
Const f(const matrix &); All cases For N parameters, 2N
Reference AND implementations
AND f(matrix &&);
Rvalue
Reference
Perfect template <class T> When you need a When you don’t want a
Forwarding f(T&&); template template (complex use of
(T &&) enable_if)
Can we do better?
28. » A type that
1. Binds to lvalues as const reference
2. Binds to rvalues as rvalue reference
3. Determines lvalue/rvalue at run-time
4. Does not force the use of templates
5. Causes no code bloat
6. Is built-in
» Const reference does not satisfy #3
» Perfect forwarding does not satisfy #4
» std::reference_wrapper does not satisfy #2 and #3
30. matrix operator + (in<matrix> m1, in<matrix> m2)
{
if(m1.rvalue()) {
matrix r(m1.rget());
r += m2.get();
return r;
} » No template!
else if(m2.rvalue()) {
matrix r(m2.rget());
» Simple, self-explanatory
r += m1.get();
return r;
» Can be even shorter!
}
else {
matrix r(m1.get());
r += m2.get();
return r;
}
}
31. » Idiom useful only when copies of parameters are made
conditionally
˃ See authors blog for more details
» Not clean when ambiguous implicit conversions are
involved
˃ See authors blog for more details
» Not well-known yet
˃ May be surprising to some
» More critical eyes needed
˃ Propose in Boost?
» Should be built-in, ideally
˃ Propose a language extension for C++1y?
32.
33. class Blob {
private:
std::vector<std::string> _v;
};
» Initialize Blob Initializing Blob::_v
» C++11 std::vector has 9 constructors!
» Question: How to write Blob’s constructor(s) so that
all the vector’s constructors could be used?
int main(void)
{
const char * shapes[3] = { "Circle", "Triangle", "Square" };
Blob b1(5, "C++ Truths"); // Initialize Blob::_v with 5 strings
Blob b2(shapes, shapes+3); // Initialize Blob::_v with 3 shapes
}
34. class Blob {
std::vector<std::string> _v;
public:
template<class Arg1, class Arg2>
Blob(Arg1&& arg1, Arg2&& arg2)
Perfect forward
: _v(std::forward<Arg1>(arg1),
two parameters
std::forward<Arg2>(arg2))
{ }
};
int main(void)
{
const char * shapes[3] = { "Circle", "Triangle", "Square" };
Blob b1(5, "C++ Truths"); // OK
Blob b2(shapes, shapes+3); // OK
}
35. class Blob {
std::vector<std::string> _v;
public:
template<class Arg1, class Arg2>
Blob(Arg1&& arg1, Arg2&& arg2)
: _v(std::forward<Arg1>(arg1), Perfect forward
std::forward<Arg2>(arg2)) two parameters
{ }
};
int main(void)
{
Blob b3; // Default constructor. Does not compile!
Blob b4(256); // 256 empty strings. Does not compile!
}
36. class Blob {
std::vector<std::string> _v;
public:
template<class... Args>
Blob(Args&&... args)
: _v(std::forward<Args>(args)...)
{ }
};
int main(void)
{
const char * shapes[3] = { "Circle", "Triangle", "Square" };
Blob b1(5, "C++ Truths"); // OK
Blob b2(shapes, shapes+3); // OK
Blob b3; // OK
Blob b4(256); // OK
}
37. class Blob {
std::vector<std::string> _v;
std::list<double> _l
public:
template<class... Args>
Blob(Args&&... args)
: _v(???), What parameters
_l(???) are for _v and
{ } what are for _l?
};
int main(void)
{
// Initialize Blob::_v with 5 strings and
// Blob::_l with { 99.99, 99.99, 99.99 }
Blob b1(5, "C++ Truths", 3, 99.99); // Does not compile!
}
38. int main(void)
{
Blob b1(5, "C++ Truths", 3, 99.99);
}
int main(void)
{
Blob b1(std::make_tuple(5, "C++ Truths"),
std::make_tuple(3, 99.99));
}
» But std::vector and std::list don’t accept
std::tuple as a parameter to constructor
39. » Packing parameters into a tuple is easy
˃ Just call std::make_tuple
» Unpacking is easy too
˃ Use std::get<N>
int main(void)
{
std::tuple<int, double> t = std::make_tuple(3, 99.99);
std::list<int> list (std::get<0>(t), std::get<1>(t));
}
» But the number and types of parameters may vary
» We need a generic way to unpack tuples
˃ This is extraordinarily complicated
40. » If you have a tuple
t = (v1, v2, v3, …, vN)
» You need
get<0>(t), get<1>(t), get<2>(t), …, get<N-1>(t)
At Compile Time!
41. » If you have a tuple
t = (v1, v2, v3, …, vN)
» And if you have another type
index_tuple<0, 1, 2, …, N-1>
Tuple Indices
» Use variadic templates to unpack a tuple
get<0>(t), get<1>(t), get<2>(t), …, get<N-1>(t)
At Compile Time!
42. template <typename Tuple1,
typename Tuple2,
unsigned... Indices1,
unsigned... Indices2> Any number of indices
Blob(Tuple1 tuple1,
Tuple2 tuple2,
index_tuple<Indices1...>,
index_tuple<Indices2...>)
: _v(get<Indices1>(tuple1)...), Size of tuple must
_l(get<Indices2>(tuple2)...) match # of indices
{ }
» We need some way to get index_tuple from a tuple
» E.g., index_tuple<0, 1, 2> from tuple(v1, v2, v3)
43. » Use the one provided by your compiler!
˃ Not standard!
gcc Clang
Index Builder _Build_index_tuple __make_tuple_indices
Index Tuple _Index_tuple __tuple_indices
» Or write one yourself
˃ ~20 lines of template meta-programming
» Just Google!
45. » Avoid Copies
˃ std::make_tuple makes copies of parameters
» How do we perfect forward parameters through a
tuple?
˃ Use std::forward_as_tuple
int main(void)
{
Blob b1(std::forward_as_tuple(5, "C++ Truths"),
std::forward_as_tuple(3, 99.99));
}
std::tuple<int &&, double &&>
47. » It does!
» “Piecewise Construct”; “Emplace Construct”
» In fact, standard library has a type called
std::piecewise_construct_t
» The standard library uses this idiom in
˃ std::map
˃ std::unordered_map
˃ std::pair has a piecewise constructor!
48. » Does it break encapsulation?
˃ Arguably, yes!
» The idiom should be used selectively
˃ Suitable for value-types implemented as structs (e.g., std::pair)
˃ When you know the implementation detail precisely
+ E.g., Auto-generated C++ lasses from DTD, XSD, IDL
49. » C++ Truths
˃ Rvalue References In Constructor: When Less Is More
http://cpptruths.blogspot.com/2012/03/rvalue-references-in-constructor-when.html
˃ Perfect Forwarding of Parameter Groups in C++11
http://cpptruths.blogspot.com/2012/06/perfect-forwarding-of-parameter-groups.html
» A Sense of Design
˃ Efficient Argument Passing in C++11 (Parts 1, 2, and 3)
http://codesynthesis.com/~boris/blog/2012/06/26/efficient-argument-passing-cxx11-part2