Jan 3, 2012

[c++11] C++11 Features

Template aliases may not be specialized:
template<typename T> // from prior
using MyAllocVec = std::vector<T, MyAllocator>; // page
template<typename T>
using MyAllocVec = std::vector<T*, MyPtrAllocator>; // error!
To achieve this effect, use a traits class:
template<typename T> // primary
struct VecAllocator { // template
typedef MyAllocator type;
};
template<typename T> // specialized
struct VecAllocator<T*> { // template
typedef MyPtrAllocator type;
};
template<typename T>
using MyAllocVec = std::vector<T, typename VecAllocator<T>::type>;




===============

auto p =
std::make_shared<widget>(Widget ctor args);

===============

decltype

Fairly intuitive, but some quirks, e.g., parentheses can matter:
struct S { double d; };
const S* p;
...
decltype(p->d) x1; // double
decltype((p->d)) x2; // const double&
Quirks rarely relevant (and can be looked up when necessary).


Similarly to the sizeof operator, the operand of decltype is unevaluated.[10] Informally, the type returned by decltype(e) is deduced as follows:

If the expression e refers to a variable in local or namespace scope, a static member variable or a function parameter, then the result is that variable's or parameter's declared type
If e is a function call or an overloaded operator invocation, decltype(e) denotes the declared return type of that function
Otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e; if e is an rvalue, the result is T
These semantics were designed to fulfill the needs of generic library writers, while at the same time being intuitive for novice programmers, because the return type of decltype always matches the type of the object or function exactly as declared in the source code.[1] More formally, Rule 1 applies to unparenthesized id-expressions and class member access expressions.[11] For function calls, the deduced type is the return type of the statically chosen function, as determined by the rules for overload resolution.[12] Example:[11]

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4; // type is const double&

The reason for the difference between the latter two invocations of decltype is that the parenthesized expression (a->x) is neither an id-expression nor a member access expression, and therefore does not denote a named object.[13] Because the expression is an lvalue, its deduced type is "reference to the type of the expression", or const double&.[10]

In December 2008, a concern was raised to the committee by Jaakko Järvi over the inability to use decltype to form a qualified-id,[14] which is inconsistent with the intent that decltype(e) should be treated "as if it were a typedef-name".[15] While commenting on the formal Committee Draft for C++0x, the Japanese ISO member body noted that "a scope operator(::) cannot be applied to decltype, but it should be. It would be useful in the case to obtain member type(nested-type) from an instance as follows":[16]
vector v;
decltype(v)::value_type i = 0; // int i = 0;
This, and similar issues pertaining to the wording inhibiting the use of decltype in the declaration of a derived class and in a destructor call, were addressed by David Vandevoorde, and voted into the working paper in March 2010.





===============
Parameter Packs

Manipulation based on recursive “first”/”rest” manipulation:
Primary operation is unpack via …:

template < typename… Types > // declare liststruct
Count; // walking template
template<typename T, typename… Rest>// walk list
struct Count<T, Rest…>{
const static int value = Count<Rest…>::value +1;
};
template<> struct Count<> // recognize end of
{ // list
const static int value = 0;
};
auto count1 = Count<int, double, char>::value; // count1 = 3
auto count2 = Count<>::value; // count2 = 0
------------------------
Count purely an exercise; C++0x’s sizeof…does the same thing:
template<typename… Types>
struct VerifyCount {
static_assert(Count<Types…>::value == sizeof…(Types),
"Count<T>::value != sizeof…(T)");
};
Unpack (…) and sizeof…only two operations for parameter packs.

======================
explicit Conversion Functions
explicit now applicable to conversion functions:
class Widget {
public:
explicit Widget(int i); // C++98 and C++0x
…
explicit operator std::string() const; // C++0x only
};
Behavior analogous to that of constructors:
void fw(const Widget& w);
int i;
…
fw(i); // error!
fw(static_cast<Widget>(i)); // okay
void fs(const std::string& s);
Widget w;
…
fs(w); // error!
fs(static_cast<std::string>(w)); // okay
======================

make_shared (<memory>)

template<class Type, class... Types>
shared_ptr<Type> make_shared(
Types&&... _Args
);

Why use this make_shared instead of operator new?

The following is a typical use shared_ptr.
shared_ptr<int> sp = new int(0xc0de);
This results in two memory allocations on the heap. One for the int and one for the reference count, even though both share the same lifetime. Using make_shared only one chunk of memory is allocate that coves both the int and the reference count. Also, you don't have to type int twice when it is implied ;).
auto sp = make_shared<int>(0xc0de);

====================
Perfect forwarding isn’t really perfect. There are several kinds of arguments that cannot be
perfectly forwarded, including (but not necessarily limited to):

• 0 as a null pointer constant.
• Names of function templates (e.g., std::endl and other manipulators).
• Braced initializer lists.
• In-class initialized const static data members lacking an out-of-class definition.
•Bit fields.

For details consult the comp.std.c++ discussion, “ Perfect Forwarding Failure Cases,”
referenced in the Further Information section of the course.

More readings

==========
unique_ptr vs. shared_ptr

Already noted:
 Deleter type part of unique_ptr type, not shared_ptr type.
 unique_ptr supports arrays, shared_ptr doesn’t.
 Both support incomplete types.
In addition:
 shared_ptr supports static_pointer_cast, const_pointer_cast,
dynamic_pointer_cast; unique_ptr doesn’t.
 No unique_ptr analogue to make_shared/allocate_shared.

===========

tie

tie can perform the work of multiple gets:
std::tie(empName, empAddr, empHDate) = // assign to all 3
employeeInfo(eid); // variables
ignore can be used within tie to get only selected elements:
std::tie(empName, empAddr, std::ignore) = // assign only name
employeeInfo(eid); // and address
std::tie(std::ignore, // assign address only.
empAddr, // (Here, using get
std::ignore) = employeeInfo(eid); // would be easier.)


make_tuple

A generalization of make_pair:
class Employee {
public:
Name name() const;
Address address() const;
Date hireDate() const;
…
};
Employee findByID(unsigned eid);
std::tuple<Name, Address, Date>
employeeInfo(unsigned employeeID)
{
Employee e(findByID(employeeID));
return std::make_tuple(e.name(), e.address(), e.hireDate());
}



Reflection
There’s support for compile-time reflection:
template<typename Tuple>
void someFunc(Tuple t)
{
std::size_t numElems = // # elems
std::tuple_size<Tuple>::value; // in Tuple
typedef
typename std::tuple_element<0, Tuple>::type // type of
FirstType; // 1st elem
…
}


=================
Fixed-Size Arrays

 Declared in <array>.
 Offers conventional members:
iterator/const_iterator/reverse_iterator and other typedefs
begin/end/cbegin/cend, empty, swap, relational operators, etc.
 But swap runs in linear (not constant) time.
 Also vectoresque members: operator[], at, front, back
 Contents layout-compatible with C arrays (and vector).
Get a pointer to elements via data (as with vector and string):
std::array<int, 5> arr; // create array
…
int *pElements = arr.data(); // get pointer to elements
=================

array vs. vector
 array is fixed-size, vector is dynamically sized.
 array uses no dynamic memory, vector does.
 array::swap is linear-time and may throw, vector::swap is
constant-time and can’t throw.
 array can be treated like a tuple, vector can’t.
=================

array vs. C Arrays
 array objects know their size, C arrays don’t
 array allows 0 elements, C arrays don’t
 array requires an explicit size, C arrays can deduce it from their
initializer
 array supports assignment, C arrays don’t
 array can be treated like a tuple, C arrays can’t
Given array, vector, and string, there is little reason to use C-style
arrays any longer.

================

function Callback Example
class ButtonHandler {
public:
…
int clicked(short upOrDown) const; // as before, but non-static
};
Button b;
ButtonHandler bh;
…
b.setCallback(std::bind(&ButtonHandler::clicked,
bh, _1)); // pass non-static member func;
// info on std::bind coming soon

_1 is actually in namespace std::placeholders, so the call to bind on this page won’t
compile as shown unless std::placeholders::_1 has been made visible (e.g., via a using
declaration). In practice, this is virtually always done in code that uses bind.
==============

From TR1: bind
Motivation:
 bind1st and bind2nd are constrained:
Bind only first or second arguments.
Bind only one argument at a time.
Can’t bind functions with reference parameters.
Require adaptable function objects.
 Often necessitates ptr_fun, mem_fun, and mem_fun_ref.
bind1st and bind2nd are deprecated in C++0x.

==============

Binding Non-Static Member Functions
bind supports this specified via:
 Object:
ButtonHandler bh;
b.setCallback(std::bind(&ButtonHandler::clicked, bh, _1));
 Pointer:
ButtonHandler *pbh;
b.setCallback(std::bind(&ButtonHandler::clicked, pbh, _1));
 Smart Pointer:
std::shared_ptr<ButtonHandler> sp;
b.setCallback(std::bind(&ButtonHandler::clicked, sp, _1));
std::unique_ptr<ButtonHandler> up;
b.setCallback(std::bind(&ButtonHandler::clicked, std::ref(up), _1));
MyCustomSmartPtr<ButtonHandler> mcsp;
b.setCallback(std::bind(&ButtonHandler::clicked, mcsp, _1));


std::unique_ptr must be wrapped by std::ref when bound, because std::unique_ptr isn’t
copyable. (It’s only movable.)

Any smart pointer will work with bind as long as it defines operator* in the conventional
manner, i.e., to return a reference to the pointed-to object. (This implies that std::weak_ptr
won’t work with bind.)


===============

Lambdas vs. bind
Lambdas typically generate better code.
 Calls through bind involve function pointers ⇒ no inlining.
Calls through closures allow full inlining.

===============

New Algorithms for C++11

R is a range, e is an element, p is a predicate, v is a value:
partition_copy copy all e in R to 1 of 2 destinations per p(e)
is_partitioned is R partitioned per p?
partition_point find first e in R where p(e) is false
is_sorted is R sorted?
is_sorted_until find first out-of-order e in R
is_heap do elements in R form a heap?
is_heap_until find first out-of-heap-order e in R
move like copy, but each e in R is moved
move_backward like copy_backward, but each e in R is moved
 std::move_iterator turns copying algorithms into moves, e.g.:
std::copy_if( std::move_iterator(b), // ≡ std::copy_if(b, e, p),
std::move_iterator(e), // but moves instead of
p);

==============

Extended C++98 Algorithms in C++11
swap New overload taking arrays
min New overloads taking initializer lists
max New overloads taking initializer lists

==============
The this pointer is a named object, but it’s defined to be an rvalue expression.
Per [expr.prim.general] (5.1.1/1 in N3290) literals (other than string literals) are rvalues,
too, but those types don’t define move operations, so they are not relevant for purposes of
this discussion. User-defined literals yield calls to literal operator functions, and the
temporaries returned from such functions are rvalues, so user-defined literals are rvalues,
too, but not rvalues any different from any other temporary returned from a function, so
they don’t require any special consideration.

==============
R-Value
There was a time in draft C++0x when lvalues were permitted to bind to rvalue references,
and some compilers (e.g., gcc 4.3 and 4.4 (but not 4.5), VC10 beta 1 (but not beta 2 or
subsequent releases)) implemented this behavior. This is sometimes known as "version 1
of rvalue references." Motivated by N2812, the rules were changed such that lvalues may
not bind to rvalue references, sometimes called "version 2 of rvalue references."
Developers need to be aware that some older compilers supporting rvalue references may
implement the "version 1" rules instead of the version 2 rules.

==============

Rvalue References and const

C++ remains const-correct:

 const lvalues/rvalues bind only to references-to-const.

But rvalue-references-to-const are essentially useless.

 Rvalue references designed for two specific problems:
Move semantics
Perfect forwarding
 C++0x language rules carefully crafted for these needs.
rvalue-refs-to-const not considered in these rules.
 const T&&s are legal, but not designed to be useful.
Uses already emerging :-)

==============

Rvalue References and const

Implications:
 Don't declare const T&& parameters.
You wouldn’t be able to move from them, anyway.
Hence this (from a prior slide) rarely makes sense:

void f3(const TVec&&); // legal, rarely reasonable
 Avoid creating const rvalues.
They can’t bind to T&& parameters.
E.g., avoid const function return types:
 This is a change from C++98.
class Rational { … };
const Rational operator+( const Rational&, // legal, but
const Rational&); // poor design
Rational operator+(const Rational&, // better design
const Rational&);

=============
Why move Rather Than Cast?

std::move uses implicit type deduction. Consider:
template<typename It>
void someAlgorithm(It begin, It end)
{
// permit move from *begin to temp, static_cast version
auto temp1 =
static_cast<typename std::iterator_traits<It>::value_type&&>(*begin);
// same thing, C-style cast version
auto temp2 = (typename std::iterator_traits<It>::value_type&&)*begin;
// same thing, std::move version
auto temp3 = std::move(*begin);
...
}

==============
Reference Collapsing in Templates

In C++98, given

template<typename T>
void f(T& param);
int x;
f<int&>(x); // T is int&

f is initially instantiated as

void f(int& & param); // reference to reference

C++98’s reference-collapsing rule says

 T& & ⇒ T&

so f’s instantiation is actually:
void f(int& param); // after reference collapsing


C++0x’s rules take rvalue references into account:

 T& & ⇒ T& // from C++98
 T&& & ⇒ T& // new for C++0x
 T& && ⇒ T& // new for C++0x
 T&& && ⇒ T&& // new for C++0x

Summary:
 Reference collapsing involving a & is always T&.
 Reference collapsing involving only && is T&&.

=================

T&& Parameter Deduction in Templates
Given

template<typename T>
void f(T&& param); // note non-const rvalue reference

T’s deduced type depends on what’s passed to param:

 Lvalue ⇒ T is an lvalue reference (T&)
 Rvalue ⇒ T is a non-reference (T)

In conjunction with reference collapsing:
int x;
f(x); // lvalue: generates f<int&>(int& &&),
// calls f<int&>(int&)
f(10); // rvalue: generates/calls f<int>(int&&)
TVec vt;
f(vt); // lvalue: generates f<TVec&>(TVec& &&),
// calls f<TVec&>(TVec&)
f(createTVec()); // rvalue: generates/calls f<TVec>(TVec&&)

==================
T&& Parameters in Templates

T&&-taking function templates should be read as “takes anything:”
template
void f(T&& param); // takes anything: lvalue or rvalue,
// const or non-const

 Lvalues can’t bind to rvalue references, but param may bind to
an lvalue.
After instantiation, param’s type may be T&, not T&&.
 Important for perfect forwarding (described shortly).
T&& as a “takes anything” parameter applies only to templates!
 For functions, a && parameter binds only to non-const rvalues:
void f(Widget&& param); // takes only non-const rvalues

==================

auto&& ≡ T&&

auto type deduction ≡ template type deduction, so an auto&&
variable’s type may be an lvalue reference:
int calcVal();
int x;
auto&& v1 = calcVal(); // deduce type from rvalue ⇒
// v1’s type is int&&
auto&& v2 = x; // deduce type from lvalue ⇒
// v2’s type is int&
==================

Implicitly-Generated Move Operations

Move constructor and move operator= are “special: ”
 Generated by compilers under appropriate conditions.
Conditions:
 All data members and base classes are movable.
Implicit move operations move everything.
Most types qualify:
 All built-in types (move ≡ copy).
 Most standard library types (e.g., all containers).
 Generated operations likely to maintain class invariants.
No user-declared copy or move operations.
 Custom semantics for any ⇒ default semantics inappropriate.
 Move is an optimization of copy.
No user-declared destructor.
 Often indicates presence of implicit class invariant.

===================
Destructors and Implicit Class Invariants

class Widget {
private:
std::vector v;
std::set s;
std::size_t sizeSum;
public:
~Widget() { assert(sizeSum == v.size()+s.size()); }
...
};
If Widget had implicitly-generated move operations:
{
std::vector vw;
Widget w;
... // put stuff in w’s containers
vw.push_back(std::move(w)); // move w into vw
... // no use of w
} // assert fires!

User-declared dtor ⇒ no compiler-generated move ops for Widget.

==================

Implicitly-Generated Move Operations

Examples:

class Widget1 { // copyable & movable type
private:
std::u16string name; // copyable/movable type
long long value; // copyable/movable type
public:
explicit Widget1(std::u16string n);
}; // implicit copy/move ctor;

// implicit copy/move operator=
class Widget2 { // copyable type; not movable
private:
std::u16string name;
long long value;
public:
explicit Widget2(std::u16string n);
Widget2(const Widget2& rhs); // user-declared copy ctor
}; // ⇒ no implicit move ops;
// implicit copy operator=



Custom Moving ⇒ Custom Copying

Declaring a move operation prevents generation of copy operations.
 Custom move semantics ⇒ custom copy semantics.
Move is an optimization of copy.
class Widget3 { // movable type; not copyable
private:
std::u16string name;
long long value;
public:
explicit Widget3(std::u16string n);
Widget3(Widget3&& rhs); // user-declared move ctor
// ⇒ no implicit copy ops;
Widget3& // user-declared move op=
operator=(Widget3&& rhs); // ⇒ no implicit copy ops
};

===============

Implicit Copy Operations Revisited
Rules for implicit copy operations can lead to trouble:

class ProblemSince1983 { // copyable class
public:
~ProblemSince1983() { delete p; } // implicit invariant:
// p owns *p
... // no copy ops
// declared
private:
int *p;
};
{ // some scope
ProblemSince1983 prob1;
...
ProblemSince1983 prob2(prob1);
...
}

// double delete!!!!!


Implicit Copy Operations Revisited

Ideally, rules for copying would mirror rules for moving, i.e.,
 Declaring a custom move op ⇒ no implicit copy ops.
Already true.
 Declaring any copy op ⇒ no implicit copy ops.
Too big a change for C++0x.
 Declaring a destructor ⇒ no implicit copy ops.
Too big a change for C++0x.
However:
 Implicit copy ops deprecated in classes with user-declared copy,
move, or dtor operations.
Compilers may issue warnings.

=================

default Member Functions
The “special” member functions are implicity generated if used:
 Default constructor
Only if no user-declared constructors.
 Destructor
 Copy operations (copy constructor, copy operator=)
Only if move operations not user-declared.
 Move operations (move constructor, move operator=)
Only if copy operations not user-declared.

================

default Member Functions
Generated versions are:
 Public
 Inline
 Non-explicit
defaulted member functions have:
 User-specified declarations with the usual compiler-generated
implementations.

==============
default Member Functions
Typical use: “unsuppress” implicitly-generated functions:

class Widget {
public:
Widget(const Widget&); // copy ctor prevents implicitly-
// declared default ctor and
// move ops
Widget() = default; // declare default ctor, use
// default impl.
Widget(Widget&&) = default; // declare move ctor, use
// default impl.
...
};



default Member Functions

Or change “normal” accessibility, explicitness, virtualness:
class Widget {
public:
virtual ~Widget() = default; // declare as virtual
explicit Widget(const Widget&) = default; // declare as explicit
private:
Widget& operator=(Widget&&) = default; // declare as private
...
};

===================

delete Functions

deleted functions are declared, but can’t be used.
 Most common application: prevent object copying:

class Widget {
Widget(const Widget&) = delete; // declare and
Widget& operator=(const Widget&) = delete; // make uncallable
…
};
 Note that Widget isn’t movable, either.
 Declaring copy operations suppresses implicit move operations!
 It works both ways:
class Gadget {
Gadget(Gadget&&) = delete; // these also
Gadget& operator=(Gadget&&) = delete; // suppress copy
… // operations
};

Delete Functions

Not limited to member functions.
 Another common application: control argument conversions.
deleted functions are declared, hence participate in overload
resolution:

void f(void*); // f callable with any ptr type
void f(const char*) = delete; // f uncallable with [const] char*
auto p1 = new std::list<int>; // p1 is of type std::list*
extern char *p2;
…
f(p1); // fine, calls f(void*)
f(p2); // error! f(const char*) unavailable
f("Fahrvergnügen"); // error!
f(u"Fahrvergnügen"); // fine (char16_t* ≠ char*)



Default Member Initialization
Default initializers for non-static data members may now be given:

class Widget {
private:
int x = 5;
std::string id = defaultID();
};
Widget w1; // w1.x initialized to 5,
// w1.id initialized per defaultID.
Uniform initialization syntax is also allowed:
class Widget { // semantically identical to above
…
private:
int x {5}; // "=" is not required,
std::string id = {defaultID()}; // but is allowed
};
Widget w2; // same as above


Default member initialization values may depend on one another:
class Widget {
private:
int x { 15 };
int y { 2 * x };
...
};


Delegating Constructors
Delegating constructors call other constructors:
class Base { … }; // as before
class Widget: public Base {
public:
Widget(): Widget(defaultFlex) {} // #1 (calls #2)
explicit Widget(double fl): Widget(0, fl) {} // #2 (calls #5)
explicit Widget(int sz): Widget(sz, defaultFlex) {} // #3 (calls #5)
Widget(const Widget& w): Widget(w.size, w.flex) {} // #4 (calls #5)
private:
Widget(int sz, double fl) // #5 (this is new)
: Base(calcBaseVal()), size(sz), flex(fl)
{ registerObject(this); }
static int calcBaseVal();
static const double defaultFlex = 1.5;
const int size;
long double flex;
};

Delegating Constructors
Delegation independent of constructor characteristics.
 Delegator and delegatee may each be inline, explicit,
public/protected/private, etc.
 Delegatees can themselves delegate.
 Delegators’ code bodies execute when delegatees return:

class Widget: public Base {
public:
Widget(const Widget& w): Widget(w.size, w.flex)
{
makeLogEntry("Widget copy constructor");
}
...
private:
Widget(int sz, double fl); // as before
...
};



Inheriting Constructors
using declarations can now be used with base class constructors:

class Base {
public:
explicit Base(int);
void f(int);
};
class Derived: public Base {
public:
using Base::f; // okay in C++98 and C++0x
using Base::Base; // okay in C++0x only; causes implicit
// declaration of Derived::Derived(int),
// which, if used, calls Base::Base(int)
void f(); // overloads inherited Base::f
Derived(int x, int y); // overloads inherited Base ctor
};
Derived d1(44); // okay in C++0x due to ctor inheritance
Derived d2(5, 10); // normal use of Derived::Derived(int, int)


Inheriting Constructors
“Inheritance” ⇒ new implicit constructors calling base class versions.
 The resulting code must be valid.
class Base {
private:
explicit Base(int);
};
class Derived: public Base {
public:
using Base::Base;
};
Derived d(10); // error! calls Derived(int), which calls
// Base(int), which is private


Summary of Features for Class Authors
 Rvalue references facilitate move semantics and perfect
forwarding.
 =default yields default body for user-declared special functions.
 =delete makes declared functions unusable.
 All data members may have default initialization values.
 Delegating constructors call other constructors.
 Inherited constructors come from base classes.

================
static_assert(sizeof(void*)==sizeof(long),
"Pointers and longs are different sizes");

================
Variadic Templates

Templates may now take arbitrary numbers and types of parameters:
template <class... Types> // std::tuple is in C++0x
class tuple;
template<class T, class... Args> // std::make_shared is
shared_ptr<T> // in C++0x
make_shared(Args&&... params);
Non-type parameters also okay:
template<typename T, std::size_t… Dims> // this template is
class MultiDimensionalArray; // not in C++0x
Whitespace around “…” not significant:
template <class ... Types> // Same meaning as
class tuple; // above
template<class T, class ...Args> // Ditto
shared_ptr<T>
make_shared(Args&&... params);



Parameter Packs
Two kinds:
 Template: hold variadic template parameters.
 Function: hold corresponding function parameters.

template <class... Types> // template param. pack
class tuple { … };
template<class T, class... Args> // template param. pack
shared_ptr<T>
make_shared(Args&&... params); // function param. pack
std::tuple<int, int, std::string> t1; // Types = int, int, std::string
auto p1 = std::make_shared<Widget>(10); // Args/params = int/int&&
int x;
const std::string s("Variadic Fun");
…
auto p2 = std::make_shared<Widget>(x, s); // Args/params =
// int&, const std::string&/
// int&, const std::string&



Parameter Packs

Count purely an exercise; C++0x’s sizeof…does the same thing:
template<typename… Types>
struct VerifyCount {
static_assert(Count<Types…>::value == sizeof…(Types),
"Count<T>::value != sizeof…(T)");
};
Unpack (…) and sizeof…only two operations for parameter packs.

Unpacking Patterns
template<typename T> // return a normalized
T normalize(T&& obj); // copy of obj
template<typename F, typename… PTypes>
void normalizedCall( F func,
PTypes&&… params) // as before
{
func(normalize(params)…); // call normalize on
} // each unpacked elem

Call to func expands to:

func(normalize(params1), normalize(params2), ..., normalize(paramsn));


====================

Variadic Class Templates
Foundational for TMP (template metaprogramming). Examples:
 Numerical computations similar to Count:
Max size of types in a list (e.g., for a discriminated union).
 Type computations:
<type_traits> has template <class... T> struct common_type;
 Object structure generation:
std::tuple<T1, T2, …, Tn> needs n fields, each of correct type.
 std::tuple<T1, T2, …, Tn> inherits from std::tuple<T2, T3,…, Tn>




====================


decltype

Yields the type of an expression without evaluating it.

int x, *ptr;
decltype(x) i1; // i1’s type is int
decltype(ptr) p1; // p1’s type is int*
std::size_t sz = sizeof(decltype(ptr[44])); // sz = sizeof(int);
// ptr[44] not evaluated

Fairly intuitive, but some quirks, e.g., parentheses can matter:

struct S { double d; };
const S* p;
...
decltype(p->d) x1; // double
decltype((p->d)) x2; // const double&

 Quirks rarely relevant (and can be looked up when necessary).


decltype
Primary use: template return types that depend on parameter types.
 Common for forwarding templates:

template<typename F, typename… Ts> // logAndInvoke
auto logAndInvoke(std::ostream& os, // returns what
F&& func, Ts&&… args) -> // func(args...) does.
decltype(func(args…)) // not quite right
{
os << std::chrono::system_clock::now(); // from new time lib
return func(args…); // not quite right
}

 Also in math-related libraries:
template<typename T1, typename T2> // mult’s return type
auto mult(T1&& a, T2&& b) -> // is same as a*b’s.
decltype(a * b) // not quite right
{
return a * b; // not quite right
}


For the code on this page to be correct, we need to add uses of std::forward in various
places. Hence the comments that say “not quite right”. The correct code is shown shortly.




注意! decltype is compile time, 故不知道 a, b 的真實, dynamic run type,
所以這樣的返回 type 可能為錯誤的!!


The Forwarding Problem

template<typename F, typename… Ts> // as before
auto logAndInvoke(std::ostream& os,
F&& func, Ts&&… args) ->
decltype(func(args…)) // not quite right
{
os << std::chrono::system_clock::now();
return func(args…); // not quite right
}

args... are lvalues, but logAndInvoke’s caller may have passed rvalues:
 Templates can distinguish rvalues from lvalues.
 logAndInvoke might call the wrong overload of func.


Perfect Forwarding Redux
Solution is perfect forwarding:

template<typename F, typename… Ts> // return type is
auto logAndInvoke(std::ostream& os, // same as func’s
F&& func, Ts&&… args) -> // on original args
decltype(func(std::forward<Ts>(args)…))
{
os << std::chrono::system_clock::now();
return func(std::forward<Ts>(args)...);
}


In the expression “std::forward<Ts>(args)…”, the pattern being unpacked is
“std::forward<Ts>(args)”, so “std::forward<Ts>(args)…” is equivalent to
“std::forward<Ts1>(args1), std::forward<Ts2>(args2), … , std::forward<Tsn>(argsn)”.




Perfect Forwarding Redux

A correct version of mult:
template<typename T1, typename T2>
auto mult(T1&& a, T2&& b) ->
decltype(std::forward<T1>(a) * std::forward<T2>(b))
{
return std::forward<T1>(a) * std::forward<T2>(b);
}

decltype vs. auto

To declare objects, decltype can replace auto, but more verbosely:
std::vector vs;
…
auto i = vs.begin();
decltype(vs.begin()) i = vs.begin();


Only decltype solves the template-function-return-type problem.
auto is for everybody. decltype is primarily for template authors.



More C++0x Features
 Enum enhancements:
Forward declaration
Specification of underlying type
Enumerant names scoped to the enum
No implicit conversion to int
 Unrestricted unions (members may be any non-reference type).
 Time library supportings clocks, durations, points in time.
 Local types allowed as template arguments.
 C99 compatibility, e.g., long long, __func__, etc.
 Inline namespaces facilitate library versioning.
 Scoped allocators allow containers and their elements to use
different allocators, e.g., vector.



Still More C++0x Features
 Generalized constant expressions (constexpr).
 User-defined literals (e.g., 10_km, 30_sec).
 Relaxed POD type definition; new standard layout types.
 extern templates for control of implicit template instantiation.
 sizeof applicable to class data members alone (e.g., sizeof(C::m)).
 & and && member functions.
 Relaxed rules for in-class initialization of static data members.
 Contextual keywords for alignment control and constraining
virtual function overrides.
 Attributes express special optimization opportunities and
provide a standard syntax for platform-specific extensions.


Removed and Deprecated Features
 auto as a storage class has been removed.
 export as a language feature has been removed.
export remains a keyword (with no semantics).
 register as a storage class has been deprecated.
 Exception specifications have been deprecated.
noexcept conceptually replaces the “throw()” specification.
 auto_ptr is deprecated. (Use unique_ptr instead.)
 bind1st/bind2nd are deprecated. (Use bind or lambdas instead.)




Override Control
Using Contextual Keywords


C++11 - the recently approved new ISO C++ standard C++11 FAQ

=================
auto for Type Declarations
For variables not explicitly declared to be a reference:
 Top-level consts/volatiles in the initializing type are ignored.
 Array and function names in initializing types decay to pointers.
const std::list<int> li;
auto v1 = li; // v1: std::list<int>
auto& v2 = li; // v2: const std::list<int>&
float data[BufSize];
auto v3 = data; // v3: float*
auto& v4 = data; // v4: float (&)[BufSize]
Examples from earlier:
auto x1 = 10; // x1: int
std::map<int, std::string> m;
auto i1 = m.begin(); // i1: std::map<int, std::string>::iterator
const auto *x2 = &x1; // x2: const int* (const isn’t top-level)
const auto& i2 = m; // i2: const std::map<int, std::string>&
auto ci = m.cbegin(); // ci: std::map<int, std::string>::const_iterator
===============

C++ remains const-correct:
 const lvalues/rvalues bind only to references-to-const.
But rvalue-references-to-const are essentially useless.

===============
Rvalue References and const
Implications:
 Don't declare const T&& parameters.
You wouldn’t be able to move from them, anyway.
Hence this (from a prior slide) rarely makes sense:
void f3(const TVec&&); // legal, rarely reasonable
 Avoid creating const rvalues.
They can’t bind to T&& parameters.
E.g., avoid const function return types:
 This is a change from C++98.
class Rational { … };
const Rational operator+( const Rational&, // legal, but
const Rational&); // poor design
Rational operator+(const Rational&, // better design
const Rational&);

=========================
The relationship between auto and decltype

captured here.
=========================

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.