constexpr:
-
Specifies that the value of a variable or function can apper in constant expression.
conexpr variable Requirements:
- Its type must be a LiteralType.
- It must be immediately initialized.
- The full-expression of its initialization, including all implicit conversions, constructor calls etc, must be a constant exprssion.
-
conexpr function requirements:
- It must not be virtual.
- Its return type must be LiteralType
- each of its parameters must be LiteralType.
-
Syntax:
template<Class R, Class....Args> class function<R(Args...)>;
-
Class template std:: function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy and invoke any Callable target--functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.
-
Member functions:
- (constructor), (destructor), operator=, swap, assign(C++17), operator bool, operator()
-
Target access:
- target_type: obtain the typeid of the stored target
- target: obtains a pointer to the stored target
-
Non-member functions:
- std::swap, operator==, operator!=
-
Usage:
- store a normal function function<void(int)> func = void (int a) { return ++a;};
- store a lambda function<sizt_t(Cumstomer&, Customer&)> func = [](Cumstomer &c, Customer &s) ->size_t{ return 0;};
- store the result of a call to std::bind function<void()> func = std::bind(print_num, 3317);
- store a call to a member function function<void(const Foo&, int)> func = &Foo::print_add; // get the address of the memeber functioin
- store a template function template T g_Minux(T i, T j) { return i - j;} function<int(int, int)> func = g_Minux;
- store a function object struct Minux { int operator()(int, int j) { return i - j; } } function<int(int, int)> func = Minux();
- glvalue: whose evaluation determine the identity of an object, bit-field, or function;
- prvalue: The pure value is the part of the traditional rvalue, is the middle value of the expression, can not take address.
- xvalue: demise value is generated by the rvalue. rvalue must be destroyed after the expression has ended. such as return x (x will be destroyed after copy), 1 + 2 (the middle value 3 will be destroyed).
- lvalue: a string literal, such as "Hello, world!";
- rvalue:
Syntax:
decltype(entity);
decltype(expression);
Inspects the declared type of the entity or the type and value category of an expression.
- If the argument is an unparenthesized id-expression a naming a structed binding, the decltype yields the reference type. (described in the specification of the structed binding declaration). (C++17)
- If the argument is an unparenthsized id-expression or an unparenthesized class member access expression, then decltype yields the type of the entity named by this expression. If there is no such entity, or if the argument names a set of overloaded functions, the program is ill-formed.
- If the argument is any of other expression of type T, and
- if the value category of expression is xvalue, then decltype yields T&&;
- if the value category of expression is lvalue, then decltype yields T&;
- if the value category of expression is prvalue, then decltype yields T.
If an expression is a function call which returns a prvalue of class type or is a comma expression whose right operand is such a function call, a temporary object is not introduced for materialized from that prvalue.
If the name of an object is parenthesized, it is treated as an ordinary lvalue expression.
-
We can't change the value the of const object after we create it, so it must be initialized.
-
But we can the const variable via a pointer, the value of the addr pointer pointes is changed, but the const variable value is not changed.
int a = 10; int *p = (int *)&a; *p = 20; // a is 10, *p is 20
-
initialize with const:
- By default, const objects are local to file.
- The compiler will replaces the uses of the const variable with its value during compile time.
-
Reference to const: To bound a reference to const we must use reference to const.
const int i = 12; const int &j = i; int &k = i; // error: nonconst reference to a const object
-
initialization and reference to const: There are two exceptions to the rule that the type of the reference must match the type of the object to which it refers:
- We can initialize the reference to const from the type that can be converted to the type of reference.
float a = 3.4; const int &b = 3; // ok, b is 3 int &c = a; // error: invalid initialization of type of reference of type int & from int
- A reference to const may refer to an object that is nonconst.
- We can initialize the reference to const from the type that can be converted to the type of reference.
-
pointer and const:
- pointer to const: the rule is the same as reference.
- const pointer:
-
top-lever const:
-
constexpr and const expression:
- Wheather a given object is a constant expression depends on the types and initializers.
- Variables declared as constexpr are implicitly const and must be initialized by const expression.
- It's good idea to use constexpr for variable that intend to use it as constant expression.
-
pointer and constexpr:
- constexpr specifier applies to pointer, not the type to which the pointer points.
- auto:
- Auto type deduction is template type deduction.
- The treatment of braced initializers is the only way where auto type deduction and template type deduction differ.
- Auto in a function return type or a labmda parameter implies template type deduction not auto type deduction.
- Auto ordinarily ignores the top-level consts.
- When use reference, we are really using the object to which it refers.
- When we ask for a reference to auto-deduced type, top-level consts in the initializer are not ignored.
const int ci = i, &cr = i; auto b = ci; // int auto c = i; // int auto d = &ci; // const int * (& of const object is a low-lever const) auto e = &i; // int * (& of int object is int *) auto &h = ci; // const int &, reference to auto-deduced, consts doesn't ignore auto &f = 42; // error: cannot bind a plain reference to literal const auto &j = 42; // ok
- delctype:
decltype((variale)); // always a reference decltype(variable); // reference only when variable is a reference
Array:
- Dimension of arrays must be a constant expression.
- A default-initialized array have undefined values. Random value given by compiler. eg: int arr[10];
- Character arrays can be initialized from a string literal. But string literal end with a null character, so array size must be bigger than the number of explicit elements.
char arr[5] = "hello"; // error: dimension must be at least 6;
- No copy and assignment for arrays initialization.
- It’s much more easier to understand array declaration from right to left and from inside to out.
- begin() and end() function which are defined in iterator header return pointers to the first and the one past the last element in an array.
- The result of subtracting two pointers is a library type named
ptrdiff_t
(difference_type C++ container library) which is defined incstddef
header file and is a signed integer type. - The result of subtracting two null pointers is 0.
- Unlike subscripts for vector and string (Library type such as vector and string that have subscript operator force the index used with subscript to be an unsigned value), the index for built-in subscript operator is not an unsigned value.
- To use a multidimensional array in a range for, the loop control variable for all but the innermost array must be references. (As with any array, when we use the name of a multidimensional array, it’s automatically converted to pointer to the first element of that array.)
- Default Arguements:
- If a parameter has default arguement, all the parameter follow it must also have default arguement.
- A function can be declared mulitple times, but it can only have default specified once in a given scope.
- Defaults can only be specified if all paramters to the right already have defaults.
- Inline and constexpr function:
-
A function defined entirely inside class/struct/union definition, whether it's a member or non-member friend funtion is implicitly an inline function.
-
Inline function avoid function call overhead, it is expanded "in line" at each call.
-
Inline specification is just a request to compiler.
-
Constexpr function restriction:
- Return type and type of each parameter must be a literal type.
- Function body must contain exactly one return statement.
-
Inorder to expand function immediately, constexpr functions are implicitly inline.
-
Inline function bans to use loop(for, while), switch, exception interface declaration and recursive function.
-
Some classes can’t rely on synthesized default constructor:
- Compiler generate default constructor only if we do not define any other constructors.
- Synthesized default initialization may do wrong things. Build-in or compound types can be initialized by undefined values.
- Compiler is unable to synthesize one default constructor. If a class has a member that has class type which doesn’t have a default constructor, so the compiler can’t initialize that member.
Benefits of Encapsulation:
- User code cannot inadvertently corrupt the state of an encapsulated object.
- The implementation of an encapsulated object can change over time without requiring changes in user-level code.
-
Defining a type member
typedef std::string::size_type pos; // typedef using pos = std::string::size_type; // type alias
-
Making Members inline
- It’s legal to specify inline on both the declaration and definition that both inside or outside the class body.
-
Overloading Functions
- Overloading functions differ by numbers and/or types of parameters
-
Mutable Data Member
- A mutable data member is never const, even it’s a member of a const object, we can modify a mutable data member inside a const function.
-
Initializers for Data Member of Class Type
- In-class initializer followed by an = sign or curly brace
- A const member function returns *this as a reference to const
Screen &display (ostream &os) {do_display(os); return *this; } const Screen &display (ostream &os) {do_display(os); return *this; } void do_display(ostream &os) {cout << contents << endl; }
- When non-const version of display call do_display, it’s this pointer is implicitly converted from a pointer to non-const to a pointer to const
- When we call display on an object, whether the object is const determines which version of display is called
Friendship Between Classes
- The members of friend class can access all members of class granting friendship
- Class and nonmember functions need not have been declared before they are used in a friend declaration.
- When a name is first appears in a friend declaration, it’s implicitly assumed to be part of surrounding scope.
- However, the friend is not actually declared in that scope.
- Even if we define the function inside the class, we must provide declaration out the class itself to make that function visible.
struct X {
friend void f() { return; } // friend function defined inside class
x() {f();} // error: f is not declared
void g();
void h();
};
void X::g() { f(); } // error: f hasn’t been declared
void f(); // declare f function
void X::h() { f(); } // ok: declaration of f is now in scope
Scope and Member Defined outside the Class
- Once the class name is seen, the remainder of class including the parameter list and function body is in scope of the class
- If a member function is defined outside the class, any name used in return type is out the scope of class.
- As a result, we must specify the class of which it is a member.
// ScreenIndex which is a type name defined inside Window_mgr class Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen& s) { }
- Class definition are processed in two phases:
- Members declaration are processed first
- Members body are processed after entire class has been seen
- Name Lookup for Class Member Declaration
- Name used in declaration, including names used for return type and types in parameter list, must see before they are used.
- Type Name Are Special
- An inner scope can redefine a name from outer scope, but if that name is a type, the class may not subsequently redefine that name.
- Normal Block-Scope Name Lookup inside Member Function
- It’s a bad idea to use same name for a parameter and a member
- After Class Scope, Look in the Surrounding Scope
- Names Are Resolved Where They Appear Within a File
-
Constructor Initializers Sometimes Are Required
- We must use constructor initializer list to provide values for members that are
const
orreferences
or that are of a Class type thatdoes not define a default constructor
- We must use constructor initializer list to provide values for members that are
-
Order of Members Initialization
- A constructor that supplies default arguments for all its parameters also defines the default constructor
FOO(string name = "XXX", string sex = "man", int age = 20) : name(name), sex(sex), age(age) {}
Sales_data(): Sales_data("", 0, 0);
Sales_data(string s): Sales_data(s, 0, 0);
- Default initialization happens:
- When we define non-static variables or arrays at block scope without initializer
- When a class has members of class type uses synthesized default constructors
- When a class member are not explicitly initialized in a constructor initializer list
- Value initialization happens:
- During array initialization we provide fewer initializer than the size of array
- When we define a local static object without initializer
- When we explicitly request value initialization by writing the expression of the form of T(), where T is the name of a type
- One class-type conversion is allowed
// item.combine(Sales_data sd); // error: "999-999" convert to string then convert to Sales_data item.combine("999-999"); // ok item.combine( (string)"999-999" ); item.combine( (Sales_data)"999-999" );
- Class-type conversions Are Not Always Useful
- Suppressing Implicit Conversions Defined By Constructor
- We can suppress implicit conversions defined by constructor by declaring the constructor as explicit
explicit Sales_data( string &s) : book(s) { }; explicit Sales_data( istream &is);
Explicit
can only be used in declarations of constructors and conversion operations which constructor can be called by only one simple argument.- Explicit Constructor Can Be Used Only for Direct Initialization
- Can not declare a static member function as const and refer this in the function body
- Static members can be defined inside or outside the class body. The static keyword is used only on the declaration inside the class body not on the definition outside the class body.
- Static members should be initialized outside the class body. We can provide in-class initializers for static members that have
const integer
type and must do so for static members that areconstexpr of literal
type. If there is an initializer inside the class there shouldn’t be another one outside the class. - Even if a const static data member is initialized in the class body, the members ordinarily should be defined outside the class definition.
- IO type: ios_base::iostate
- State flag:
- ios_base::goodbit
- ios_base::badbit
- ios_base::eofbit
- ios_base::failbit
- Functions: s.good() s.bad() s.eof() s.fail() s.clear() s.clear(flag) s.rdstate()
- Once an error has accoured, subsequent IO operations on that stream will fail
- Interrogate the State of Stream
- Fail returns true if bad is set.
- Flushing the Output Buffer
- Flush: flush the buffer but add no data
- Endl: end the current line and flush the buffer
- Ends: add null character and flush the buffer
- Buffers are not flushed if the program crashes
- Typing Input And Output Stream Together
- When an input stream is tied to an output stream, any attempt to read the input stream will first flush the buffer associated with output stream
- The Open and Close Members
- Once a file is opened, the file stream remains associate the file.
- Open an opened file will fail and subsequent operations will also fail and set failbit
- Once the file is closed, we can open a new file.
- It’s good practice to verify the open succeeded.
- When a file stream object goes out the scope, the file it is bound is automatically closed.
- File Mode
- fstream::in out app(seek to the end before every write)
- ate(seek to the end immediately af open)
- fstream::binary trunc(truncate the file)
- The only way to preserve existing date in a file opened by an ofstream is specify app or in mode explicitly.
-
Common operation on shared_ptr and unique_prt
shared_prt<T> p; p->get(); p->man(); swap(m, n); unique_ptr<T> q;
-
Operation specified to shared_ptr
make_shared<T>(args); shared_ptr<T> p(q); p = q; p.unique(); p.use_count(); shared_ptr<T> p(q) shared_ptr<T> p(q, deleter) shared_ptr<T> p(u) shared_ptr<T> p(p2, d) p.reset() p.reset(q) p.reset(q, d)
-
Operation specified to unique_ptr
unique_ptr<T, D> u(q); u.release() u.reset(q)
-
Operation specified to weak_ptr
weak_ptr<T> w; weak_ptr<T> w(sp); w = p; w.reset(); w.use_count(); w.expired(); w.lock();
-
Programs tend to use dynamic memory for three purpose:
- They don’t know how many objects they need
- They don’t know the precise type of the objects they need
- They want to share data between several objects
-
If new is unable to allocate the request storage, it’ll throw an exception of type
bad_alloc
, we can usenothrow
keyword to prevent it throw such exception, the result is a null pointer. -
Smart pointer must be initialized directly, we can’t implicitly convert a built-in pointer to smart pointer
std::shared_ptr<int> p = new int(1024); // error: implicit conversion std::shared_prt<int> p(new int (1024)); // ok: user direct initialization
-
Don’t mix ordinary pointer and smart pointer
- It’s dangerous to use built-in pointer to access the object owned by smart pointer, because we may not know when the object is destroyed.
-
Don’t use get to initialize or assign another smart pointer
-
Use
get()
to pass access to the pointer to the code that will not delete the pointer. -
Smart pointer can automatically free the memory it manages when an exception occurs
-
Smart pointer pitfalls:
- Don’t use built-in pointer to initialize or set more than one smart pointer
- Don’t use get() to initialize or assign smart pointer.
- Pointer returned by get() will become invalid when last corresponding sp goes away.
- Don’t delete a pointer returned by get().
- If use sp manage resource other than memory allocated by new, pass a deleter.
- Passing and returning unique_ptr
-
There is one exception for the rule that we cannot copy or assign a unique_ptr:
- we do those when a unique_ptr is about to be destroyed.
- Dynamic Arrays
- We can use empty parentheses to value initialize elements of a dynamic array but not the initializer inside the parentheses which means that we cannot use auto to allocate an array.
- It’s legal to create an array variable of size 0
deleter [] pa; // delete a dynamic array.
- Shared_ptr does not directly support dynamic array, so:
- We must define our own deleter.
- Smart pointers do not support pointer arithmetic and subscript, to access the elements of a dynamic array we can use get() to obtain a built-in pointer.
for (size_t i = 0; i < 10; i++) { // get() obtains a normal pointer which support pointer arithmetic. *(sp.get() + i) = i; }
- The Allocator Class
- Decoupling the allocation from construction.
- Operation:
std::allocator<T> a; a.allocate(n); a.deallocate(p, n); a.construct(p, args); a.destroy(p);
- Algorithms to copy and fill the uninitialized memory
// returns a pointer pointing to the element pass to the last constructed element uninitialized_copy(b, e, p2); uninitialized_copy_n(b, n, p2); uninitialized_fill(b, e, t); uninitialized_fill_n(b, n, t);
-
A constructor is a copy constructor if its first parameter is a reference to the class type, and any additional parameters have default values.
-
If we do not define copy constructor the compiler will synthesize one for us even if we define any other constructor.
-
Difference between copy and direct initialization:
- Direct: we’re asking compiler use ordinary function matching to select the constructor that best matches the arguments we provide;
- Cope: we’re asking compiler to copy the right-hand operand to the object being created, converting if necessary.
-
Copy constructor happens:
- When we use =
- Pass an object as an argument to parameter of non-reference type
- Return an object from a function that has non-reference return type
- Brace initialize the elements of an array or elements of aggregate class
-
Why Copy construct first parameter must be a reference:
- To call copy constructor, we’d need to call copy constructor to copy the arguments, to copy the arguments, we’d need to call copy constructor and indefinitely.
-
Assignment operators ordinarily should return a reference to their left-hand operand.
-
Compilers will generate synthesized copy-assignment operator if we don’t define it’s own.
-
Some synthesized copy-assignment operator disallow assignment, otherwise, it assigns each right-hand object to the corresponding left-hand object of the class using copy-assignment operator for the type of that member.
-
Copy elision:
-
In a
return statement
, when the oparand is aprvalue
as the same class type as the return typeT f() { return T(); // f(), only one call to default constructor of T }
-
In a
initialization
of a variable, when the initializer expression is aprvalue
of the same type as the variable typeT x = T(T(f())); // only one call to default constructor of T to initialize x
-
In a
return statement
, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of thesame class type
(ignoring cv-qualification) as the function return type. This variant of copy elision is known asNRVO(named return value optimization)
. -
In the
initialization
of an object, when the source object is a nameless temporary and is of thesame class type
(ignoring cv-qualification) as the target object. When the nameless temporary is the operand of a return statement, this variant of copy elision is known asRVO(return value optimization)
. -
In a
throw-expression
, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and whose scope does not extend past the innermost try-block (if there is a try-block). -
In a
catch clause
, when the argument is of the same type (ignoring cv-qualification) as the exception object thrown, the copy of the exception object is omitted and the body of the catch clause accesses the exception object directly, as if caught by reference (there cannot be a move from the exception object because it is always an lvalue). This is disabled if such copy elision would change the observable behavior of the program for any reason other than skipping the copy constructor and the destructor of the catch clause argument (for example, if the catch clause argument is modified, and the exception object is rethrown with throw). (sicen C++11)
-
-
If a class needs a destructor, it’s almost surely it needs copy constructor and copy-assign operator.
-
Classes that need copy need assignment, and vice versa.
-
Preventing Copy: =delete
- =delete must appear on the first declaration of the deleted function.
- We can specify =delete on any function.
-
The destructor should not be deleted
- The compiler won’t let us define variables or temporaries of type that has deleted destructor.
-
The Copy-control members may be synthesized as deleted
- Deleted destructor: the class has
member
whose own destructor isdeleted or inaccessible
. - Deleted copy constructor: class has
member
whose own copy constructor is deleted or inaccessible or has member with deleted or inaccessibledestructor
. - Deleted copy-assignment operator: if a member has deleted or inaccessible copy-assignment operator, or the class has
const or reference member
. - Deleted default constructor: if the class has deleted or inaccessible constructor; or a reference member does not have an in-class initializer; or a const member whose type does not explicitly define a default constructor and that member does not have in-class initializer.
- Deleted destructor: the class has
-
Two points in mind when writing assignment operator:
- Assignment operator should work correctly if an object is
assigned to itself
- Most assignment operators share work with destructor and copy constructor
- Assignment operator should work correctly if an object is
-
Good pattern to write assignment operator:
- Copy the right-hand operand into the local temporary
- Destroy the existing members of the left-hand operand
- Copy the data from temporary into the members of the left-hand operand
HasPtr & operator= (HasPtr &rhs) { string p = new string(*rh.ps); // first step delete ps; // second step this->ps = p; // third step this->num = rhs.num; return *this; } HasPtr& operator=(HasPtr& rhs) { if (this != &rhs) { delete this->ps; this->ps = string(*rhs.ps); this->num = rhs.num; } return *this; }
-
lvalue/rvalue reference:
-
Rvalue reference may be bound only to an object that is about to be destroyed.
-
Lvalue reference refers to an
object's identity
whereas rvalue refers to anobject's value
. -
Functions that return lvalue references, along with assignment, subscript, dereference and prefix increment/decrement operators, are all examples of expression that return lvalue.
-
Functions that return rvalue reference, along with arithmetic, relational, bitwise, postfix increments/decrement operators, are all yield rvalue.
- We can bind either lvalue to const or rvalue to these expressions, and its lifetime is extended.
int &&x = 2 + 3; // x itself a lvalue, but it binds to a rvalue const int& x = 2 + 3;
- Const reference can accept both lvaue reference and rvalue reference.
- Rvalue bind to a const reference, it's lifetime is extended.
- Named rvalue is a lvalue: the variable bind to rvalue itself is lvalue.
- We can bind either lvalue to const or rvalue to these expressions, and its lifetime is extended.
-
Because rvalue reference can only be bound to temporaries, so:
- There can be no other users of that object
- These facts together mean that code uses rvalue reference is free to take over the resource from the object which the reference refers.
- The referred-to object is about to be destroyed
-
Rvalue references refer to objects that are about to be destroyed. Hence, we can "steal" the state from an object bound to rvalue reference.
-
A variable is an lvalue; we cannot bind an rvalue reference to an variable even if that variable is defined as an rvalue reference type.
-
-
std::move
- We can destroy the moved-from object and assign a new value to it, but cannot use the value of moved-from object.
Move constructor
andmove assignment
operator that cannot throw exceptions should be marked asnoexcept
.- Unless the library knows that our move constructor can't throw, it will do extra work to cater to the possibility that moving an object of our class type might throw.
- Move operation doesn't throw because two interrelated facts:
- first, although, move operations usually don't throw exceptions, they are permitted to so.
- Second, the library provide guarantees to what they do if an exception happens.
- After move operation, the moved-from object must remain a valid, destructible object but user may make no assumptions about its value.
- The compiler synthesizes the move constructor and move assignment operator only if the class doesn't define any of its copy-control members and only if all data members can be moved constructed and moved assigned, respectively.
- Move operations are never implicit defined as deleted.
- Move members are defined as deleted:
- if the class has a member that defines its own copy members but move members or if the class has a member that doesn't define its own copy members for which compiler is unable to synthesize move members.
- if the class has a member whose move members are deleted or inaccessible.
- if the
destructor
of the class is defined as deleted or inaccessible - if the class has members that are
const
orreference member
. - if ask compiler to generate a move operation by using =default, then the copy operation will be defined as deleted.
- Declaring a destructor has a potentially significant side effect: it prevents the move operations from being generated.
- Class has define its own
move constructor
or move-assignment operatormust define
its owncopy operations
. Otherwise, those members aredeleted
as default. - Lvalues are moved, lvalues are copied but r values are copied when there is no move constructor.
-
Move Iterator:
- Return rvalue reference by calling library
make_move_iterator
function which takes a normal iterator and returns a rvalue reference rather than normal iterator which return lvalue reference.
-
Rvalue LValue reference member function:
- In order to maintain backward compatibility, the library continue allow assignment to rvalue. However, we can prevent this feature by indicating rvlaue/lvalue reference in the same way that we define const member functions, we place a reference qualifier (& or &&) after the parameter list.
- A function can be both const and reference qualified, the reference must follow const.
-
Overloading and Reference Function
- If a member function has a reference qualifier, all the versions of that function with same parameter list must have reference qualifier(overloading functions have ref-qualifier).
void func() const &; void func() const &&;
- When an overloaded operator is a member function, this is bound to the left-hand operand. Member operator functions have one less (explicit) parameter than the number of operands.
- An operator function must either be a member of a class or have at least one parameter of class type.
- Ordinarily, the comma, address-of, logical AND, and logical OR operators should not be overloaded.
- Ordinarily, the first parameter of output is a non const ostream reference, non const because writing to the stream can change it state, reference because we can't copy ostream object. The second parameter should be a const reference to class type.
ostream& operator<<(ostream& os, const T& t);
- To be consistent with other output operator, operator<< returns a ostream reference.
- Output operator should print the contents of the object with mininal formatting, they shouldn't print a newline.
- Io operators mube be nonmember functions and usually be declared as friends.
Input operators must deal with possibility that input might faill. Output operators generally don't bother.
Input operator should decide what, if anything, to do about error recovery.
Ordinary, the parameter of arithmetic and relational operator should be const. An arithmetic generates a new value which is the result of computation on its two params
Classes that define both arithmetic operator and related compound assignment operator ought to implement the arithmetic operation by using the compound assignment.
If a single logical definition for < exists, classes usually should define the < operator. However, if the class also has ==, define < only if the definitions of < and == yield consistent results.
Assignment operators must, and ordinarily compound-assignment operators should, be defined as members.
These operators should return a reference to the left-hand operand.
If a class has a subscript operator, it usually should define two versions:
- one that returns a plain reference
- the other that is a const member and returns a reference to const.
To be consistent with the built-in operators, the postfix operators should return the old (unincremented or undecremented) value. The rvalue is returned as a value, not a reference. But the prefix operators return reference.
p.operator++(0); // call postfix operator
++p.operator++(); // call prefix operator++
The function-call operator must be a member function.
A class may define multiple versions of the call operator, each of which must differ as to the number or types of their parameters.
Function objects are often used in combination with the standard algorithme.
When we write a lambda, the compiler translates that expression into an unnamed object of an unnamed class which contain an overloaded function-call operator which is const function but we can use 'mutable' key word to modify the captured data members.
Classes generated from lambda expression have a deleted default constructor deleted assignment operator, and a default destructor, where has copy/move constructor depends on the type of the captured data members.
- functions
- pointer to function
- lambda
- objects created by bind
- classes that overloaded function-call operator
specifies the type returned by a call to the object and the argument type that must be passed in the call.
The Library function Type (P 577)
- Defined in functional header, represent callable objects with return type and parameter type.
- We cannot directly store the name of an overloaded function in an object of type function.
- function<int(int, int)> f2 = [](int i, int j) { return i % j; };
function:
- function f; function f(nullptr); function f(obj); f(args); result_type;
- deprecated C++17: argument_type; first_argument_type; second_argument_type;
explicit operator type() const;
A conversion function must be a member function, may not specify return type, and must have an empty parameter list, and is called automatically. The function usually should be const.
Conversion operators are applied implicitly that can yield surprising results, we can prevent this by using explicit
keyword.
explicit operator int() const { return val;} // define explicit conversion
static_cast<int>(obj); // use explicit conversion
Explicit conversion can implicitly use as:
- The condition of an if, while, do statement
- The condition expression in a for, logical NOT(!), OR(||), AND(&&), (?:).
Category:
- new/delete/new[]/delete[] operator // global, can not be overloaded
- operator new/delete/new[]/delete[]
- placement new/new[] // note: there are no placement delete/delete[]
Process of new and delete:
- new: call
operator new
to malloc memory, call constructor construct object and return pointer - delete: call destructor destruct object, free memory
- what makes virtual calls slower 🔗 Ref
- Extra indirection (pointer dereference) for each call to a virtual method.
- Virtual methods usually can’t be inlined, which may be a significant cost hit for some small methods.
- Additional pointer per object. On 64-bit systems which are prevalent these days, this is 8 bytes per object. For small objects that carry little data this may be a serious overhead.
- This can definitely play a role in some scenarios (i.e. a lot of small objects where the additional memory means less of them fit into L1 data cache).
- Template arguments used for non type template parameters must be constant expression.
- Template programs should try to minimize the number of arguments placed on the arg types.
- Function templates and member functions of class templates are ordinarily put into head file.
- It's up to the caller to guarantee that the arguments passed to the template support any operations that template uses, and that those operations behave correctly in the context in which the template uses them.
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v2 < v1) return 1;
if (v1 < v2) return -1;
return 0;
}
- Instantiating a Function Template
- Template Type Parameters
- Nontype Template Parameters
- inline and constexpr Function Templates
- Writing Type-Independent Code
- A function of a class template is instantiated only if the program uses that function.
- There is one exception to the rule that when we use a class template we must supply the template arguments that is in the scope of template itself.
- Class and it's friends can both be template or not. A class that has non-template friends grants that friends access or instantiation of the template. When friends is itself a template, the class granting friendship controls friendship includes all instantiations or specified instantiation.
- In the scope of the class template, we may refer to the template without specify the template arguments.
- In order to refer to a specific instantiation of a template, we must declare first the template itself.
template<typename> class Friend;
template<typename> class Base;
template<typename T> bool compare(const Base<T>&, const Base<T>&);
template<typename T>
class Base {
public:
friend bool compare<T>(const Base<T>&, const Base<T>&);
/* one-to-one friendShip
* Forward declaration necessary */
friend class Friend<T>;
/* Specific friendShip
* Forward declaration necessary */
friend class Friend<int>;
/* Generic friendShip
* Forward declaration unnecessary
* All instantiations are friends to each instantiation of Base */
template<typename T> friend class Friend;
};
-
References to a Template Type in the Scope of the Template
-
Member Functions of Class Templates
-
The check and Element Access Members
-
Blob Constructors
-
Instantiation of Class-Template Member Functions
-
Simplifying Use of a Template Class Name inside Class Code
-
Using a Class Template Name outside the Class Template Body
-
Class Templates and Friends
-
One-to-One Friendship
-
General and Specific Template Friendship
-
Befriending the template's own type parameter:
template<typename Type> class Bar { // if Type is class type Base, so Base is friend of Bar friend Type; // grants access to Type used to instantiate the Bar }
Whatever Type is a class or build-in type it works good.
-
Template Alias
Because template is not a type, we cannot define a typedef that refers to a template. But using:
// Syntax: using identifier attr(optional) = type-id ; template <template-parameter-list > using identifier attr(optional) = type-id ;
-
static Members of Class Templates
template<typename T> class Foo { public: static T count(T a) { return 10; } static string count() { return "yes"; } }; Foo<string> fo1; Foo<int> fo2; cout << fo1.count() << endl; cout << fo2.count(5) << endl; cout << Foo<int>::count() << endl;
[[attr]] [[attr1, attr2, attr3(args)]] [[namespace::attr(args)]] alignas_specifier Formally, the syntax is: [[ attribute-list ]] (C++11) [[ using attribute-namespace : attribute-list ]] (C++17)
where attribute-list is a comma-separated sequence of zero or more attributes (possibly ending with an ellipsis ... indicating a pack expansion) identifier identifier ( argument-list ) attribute-namespace::identifier attribute-namespace::identifier ( argument-list )
- simple attribute, such as [[noreturn]]
- attribute with arguments, such as [[deprecated("because")]]
- attribute with a namespace, such as [[gnu::unused]]
- attribute with both a namespace and an argument list
If using: namespace appears in the beginning of an attribute list, no other attributes in the attribute list can specify a namespace: the namespace specified in a using applies to them all:
- [[using CC: opt(1), debug]] // same as [[CC::opt(1), CC::debug]]
- [[using CC: CC::opt(1)]] // error: cannot combine using and scoped attribute
Storage Class Specifier
- Auto: It was allowed in object declared in block scope or in function parameter list. Auto duration.
- Register: It is allowed in object declared in block scope or in function parameter list. Auto duration.
- Extern: Used for variable and object.
- Static: It is allowed in declaration of objects, functions and anonymous unions.
- Thread_local: It is allowed in object declared at namespace scope, function declared at block scope and and static data member.
- Storage duration: automatic, static, thread, dynamic.
- Linkage: no-linkage, internal-linkage, external-linkage.
Template Parameters follow normal scoping rules.
A name used as template parameter can not be reused within template.
By default, the language assumes a name accessed through scope operator is not a type
, so we use keyword typename
to tell compiler that we are using a name that is a type name of template.
When we want to inform the compiler that a name represents a type, we must use the keyword typename, not class:
template <typename T>
typename T::value_type top(const T& c); // T is a class type
- Template Parameters and Scope
- Template Declarations
- Using Class Members That Are Types
- Default template Arguments:
template<typename T = int> void f(T a, T b); f<string>("3", "5"); f<>(2, 3); // use default argument template<class T = int> class Base; Base<string> ba; Base<> b;
- Template Default Arguments and Class Templates
- Category: member template for normal class and class template.
- When we define a member template outside of the class template we must provide class template parameter list followed function template parameter list.
- As usual, compiler typically deduce template arguments for the member template’s own parameter from arguments passed in the call.
template<typename T>
template<typename IT> void Base<T>::func(IT &a, IT &b) { return; }
Base<double> base; base.func(4, 5);
- Member Templates of Ordianary (Nontemplate) Classes
- Member Templates of Class Templates
- Instantiation and Member Templates
- References and pointers don't need instantiation.
- Compiler will instantiate a template for every file which contain this template with same arguments, In large system the overhead of this can be significant, we can avoid this through explicit instantiation.
// instantiation declaration and definition
extern template class Blob<string>; // instantiation declaration
template int compare(const int&, const int&); // instantiation definition
- An instantiation definition for a class template instantiates all the members of that template including inline member functions.
- When compiler sees an extern instantiation declaration it will not generate code for that instantiation until it sees that (non extern ) definition, so we must provide only one definition for that template instantiation declaration which may appear in multiple files of the program.
- Binding the Deleter at Run Time
- Binding the Deleter at Compile Time
A very limited number of conversions are supplied:
- const conversion: A parameter that is a reference or pointer to const can be passed a reference or pointer to non-const object.
- Array- or function-to-pointer conversion.
- Const conversion and array or function to pointer are the only automatic conversions for arguments to parameters with template types.
- Function Parameters That Use Same Template Parameter Type
- Because there are limited conversions, the arguments to parameters must have the essentially same type.
- If the deduced types do not match, then the call is an error.
- Normal Conversion for Ordinary arguments
- Specifying an Explicit Template Argument
- Explicit template argument(s) are matched to corresponding template parameter(s) from left to right.
// poor design: users must explicitly specify all three template parameters template<typename T1, typename T2, typename T3> T3 alternative_sum(T2, T1); // error: can’t infer initial template parameters auto val3 = alternative_sum<long long>(i, lng); // ok: all three parameters are explicitly specified auto val2 = alternative_sum<long long, int, long>(i, lng);
- Normal Conversions Apply for Explicitly Specified Template Arguments
- If we explicitly specify template parameter type, normal conversion apply.
template<typename T> bool compare<T, T> compare(2.3, 4); // error; compare<int>(2.3, 4); // OK
template<typename It>
auto fcn(It beg, It end) -> decltype(*beg) {
/* The dereference operator returns an lvalue, so
* return a reference to an element from the range */
return *beg;
}
- The Type Transformation Library Template Classes
/* Header: <type_traits> * must use typename to use a type member of a template parameter; see § 16.1.3 (p. 670) */ template <typename It> auto fcn2(It beg, It end) -> typename std::remove_reference<decltype(*beg)>::type { // return a copy of an element from the range return *beg; }
template<typename T>
int compare(const T&, const T&);
int(*pf)(const int&, const int&) = compare;
// The type of parameters int pf determines the type of template arguments for T.
void func(int (*) (const string&, const string&);
void func(int (*) (const int&, const int&);
func(compare); // error: which version of func is called?
func(compare<int>); // ok
When the address of a function-template instantiation is taken, the content must be such that it allows a unique type or value to be determined for each of template parameter.
- Type Deduction from Lvalue Reference Function Parameter
- When the parameter of function is a non-const ordinary reference we can only pass lvalue;
- When it’s a const reference we can pass any kind of arguments, object, temporary or literal value.
- Type Deduction from Rvalue Reference Function Parameter
- When the parameter of function is a rvalue reference we can only pass rvalue.
- Reference Collapsing and Rvalue Reference Parameter
- Reference Collapsing and Rvalue Reference Parameter
- References collapse to form rvalue reference only in the specific case of an rvalue reference to rvalue reference.
- Reference collapsing applies only when a reference to a reference is created indirectly, such as in a type alias or a template parameter.
- An argument of any type can be passed to a function parameter that is a rvalue reference to a template parameter type.
- Writing Template Functions with Rvalue Reference Parameters
Reference collapse apper in following context:
instantiation of template; generation of auto; typedef with class type; decltype
For Mod, where Mod is | If T is | Then Mod::type is |
---|---|---|
remove_reference | [X& or X&&] [otherwise] | [X] [T] |
add_const | [X&, const X, or function] [otherwise] | [T] [const T] |
add_lvalue_reference | [X&] [X&&] [otherwise] | [T] [X&] [T&] |
add_rvalue_reference | [X& or X&&] [otherwise] | [T] [T&&] |
remove_pointer | [X*] [otherwise] | [X] [T] |
add_pointer | [X& or X&&] [otherwise] | [X*] [T*] |
make_signed | [unsigned X] [otherwise] | [X] [T] |
make_unsigned | [signed type] [otherwise] | [unsigned T] [T] |
remove_extent | X[n] otherwise | X T |
remove_all_extents | X[n1][n2]... otherwise | X T |
- Writing Template Functions with Rvalue Reference Parameter
- Rvalue reference are used in one of the two contexts: either the template is forwarding it parameters or the template is overloaded.
template <typename T>
remove_reference_t<T>&& move(T&& t) {// reference collapsing may happen
// static_cast covered in § 4.11.3 (p. 163)
return static_cast<remove_reference_t<T>&&>(t);
}
template<typename T>
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
- Define Function Parameter Retain Type Information
- A function parameter that is an rvalue reference to a template type parameter can preserve constness and lvalue / rvalue property of corresponding arguments.
- Using std::forward to Preserve Type Information in a Call
- Must be used with an explicit template argument, return type is T&&.
- When used with function parameter that is an rvalue reference to template type parameter(T&&), forward represent all details about an argument’s type.
- As with std::move, it’s a good idea not to provide a using declaration for std::forward.
Function matching is affected by the presence of function template:
- The candidate functions for a call include any function-template instantiation for which template argument deduction (§ 16.2, p. 678) succeeds.
- The candidate function-templates are always viable, because template argument deduction will have eliminated any templates that are not viable.
- As usual, the viable function (template and nontemplate) are ranked by the conversions, if any, needed to make a call. Of course, the conversion used to make a call are quite limited.
- If there are several functions that provide an equally good match:
- If there is one only
nontemplate function
, the nontemplate function is the best match. - If there is no template function, the
more specialized template function
is best match. - otherwise, the call is ambiguous.
- If there is one only
- Writing Overloaded Templates
- Multiple Viable Templates
- When there are several overloaded templates that provide an equally good match, the more specialized version is prefered.
- Nontemplate and Template Overloads
- Overloaded Templates and Conversions
- Mission Declaration Can cause Program Misbehave
- Declare every function in an overloaded set before you define any of the function. That way you don’t worry whether the compiler will instantiate a call before it sees the function you intended to use.
The sizeof... Operator
// sizeof... operator can get parameter pack size
template<typename T, typename... Args>;
void foo(const T&, const Args&... rest);
Variadic function are always recursion.
/* function to end the recursion and print the last elements
* must be declared before the variadic version of print is defined */
template<typename T>
ostream& printf(ostream &os, const T &t) {
return os << t ;
}
template<typename T, typename... Args>
ostream& printf(ostream& os, const T& t, const Args&... rest) {
os << t << ",";
return printf(os, rest...); // recursion call
}
- Understanding Pack Expansions
A specialization is a separate definition of template in which one or more template parameter are specified to have particular types.
-
Defining a Function Template Specialization
template<typename T> int compare(const T&, const T&); template<> // indicate this is a fully specialization int compare(const char* const &, const char*&);
-
Function overloading Versus Template Specialization
- Specializations instantiate a template, they do not overload it. As result, they have no effect on function matching.
- In order to specialize a template, a declaration for the original template must be in the scope.
- Moreover, a declaration for a specialization must be in scope before any code use that instantiation.
- Templates and their specializations should be declared in the same header. Declaration for all template with name should appear first, followed by any specializations of those templates.
- If declaration for a specialization missing, code works good, because compiler will generate code from original template.
-
Class Template Specialization
Specialize hash template:
namespace std { template<> // indicates we are fully specializing a template struct hash<sales_data> { typedef size_t return_type; typedef sales_data arguments_type; size_t operator(const sales_data &) const; }; size_t hash<sales_data>::()(const sales_data &s) const { return hash<string>()(s.bookNo)^ hash<unsigned>()(s.units_sold)^ hash<double>()(s.revenue); } }
We can add members to namespace after open it.
-
Class-Template Partial Specialization
// A class partial specialization is itself a template. // original, most general template template<class T> struct remove_reference {typedef T type;}; // partial specializations that will be used for lvalue and rvalue references template <class T> struct remove_reference<T&> // lvalue references { typedef T type; }; template <class T> struct remove_reference<T&&> // rvalue references { typedef T type; };
-
Specializing Members But Not Class
template<typename T> struct Foo { Foo(const T &t = T()) : men(t){} void Bar(); T men; }; template<typename T> void Foo<T>::Bar() { std::cout << "general" << std::endl; } template<> // we're specializing void Foo<int>::Bar() { std::cout << "partial" << std::endl; } Foo<string> fs;// Foo<string>::Foo() fs.Bar(); // "general", Foo<string>::Bar() Foo<int> fi; // Foo<int>::Foo() fi.Bar(); // "partial", uses specialization of Foo<int>::Bar()
- At link time, identical instantiations generated by different translation units are merged.
- The definition of a template must be visible at the point of implicit instantiation, which is why template libraries typically provide all template definitions in the headers
- Syntex
template <parameter-list> declaration (1) template <parameter-list> requires-clause declaration (2) (C++20) template <parameter-list> concept concept-name = constraint-expression (4) (C++20)
- template-id
template-name <parameter-list>
template <parameter-list > declaration;
- Syntax
type name(optional) type name(optional) = default type ... name(optional) (C++11) placeholder name (C++17)
Array
andfunction
types may be written in a template declaration, but they are automatically replaced bypointer to object
andpointer to function
as appropriate.- A template parameter of the form
class Foo
is not an unnamed non-type template parameter of type Foo, even if otherwise class Foo is an elaborated type specifier andclass Foo x
; declares x to be of type Foo.
- Syntax
type-parameter-key name(optional) (1) type-parameter-key name(optional) = default (2) type-parameter-key ... name(optional) (3) (C++11) type-constraint name(optional) (4) (C++20) type-constraint name(optional) = default (5) (C++20) type-constraint ... name(optional) (6) (C++20)
- Syntax
template <parameter-list > typename(C++17)|class name(optional) (1) template <parameter-list > typename(C++17)|class name(optional) = default (2) template <parameter-list > typename(C++17)|class ... name(optional) (3) (C++11)
- The name of a template parameter is not allowed to be redeclared within its scope (including nested scopes)
- In the definition of a member of a class template that appears outside of the class template definition, the
name of a member
of the class templatehides
thename of a template parameter
of any enclosing class templates, but not a template parameter of the member if the member is a class or function template.template<class T> struct A { struct B {}; typedef void C; void f(); template<class U> void g(U); }; template<class B> void A<B>::f() { B b; // A's B, not the template parameter } template<class B> template<class C> void A<B>::g(C) { B b; // A's B, not the template parameter C c; // the template parameter C, not A's C }
- In the definition of a member of a class template that appears outside of the namespace containing the class template definition, the
name of a template parameter
hides
the name of a member of thisnamespace
.namespace N { class C {}; template<class T> class B { void f(T); }; } template<class C> void N::B<C>::f(C) { C b; // C is the template parameter, not N::C }
- In the definition of a class template or in the definition of a member of such a template that appears outside of the template definition, for each non-dependent base class, if the name of the base class or the name of a member of the base class is the same as the name of a template parameter, the
base class name
ormember name
hides
thetemplate parameter name
.struct A { struct B {}; int C; int Y; }; template<class B, class C> struct X : A { B b; // A's B C b; // error: A's C isn't a type name };
- If an argument can be interpreted as both a
type-id
and anexpression
, it is always interpreted as a type-id, even if the corresponding template parameter is non-type:template<class T> void f(); // #1 template<int I> void f(); // #2 void g() { f<int()>(); // "int()" is both a type and an expression, // calls #1 because it is interpreted as a type }
- A template
argument
for a type template parametermust be
atype-id
, which may name an incomplete type:template<typename T> class X {}; // class template struct A; // incomplete type typedef struct {} B; // type alias to an unnamed type int main() { X<A> x1; // ok: 'A' names a type X<A*> x2; // ok: 'A*' names a type X<B> x3; // ok: 'B' names a type }
-
A template argument for a template template parameter must be an
id-expression
which names a class template or a template alias. -
To match a template template argument A to a template template parameter P, P must be at least as specialized as A (C++17). If P's parameter list includes a parameter pack, zero or more template parameters (or parameter packs) from A's template parameter list are matched by it.
template<typename T> struct eval; // #1 primary template template<template<typename, typename...> class TT, typename T1, typename... Rest> struct eval<TT<T1, Rest...>> {}; // #2 partial specialization of eval template<typename T1> struct A; template<typename T1, typename T2> struct B; template<int N> struct C; template<typename T1, int N> struct D; template<typename T1, typename T2, int N = 17> struct E; eval<A<int>> eA; // ok: matches #2 eval<B<int, float>> eB; // ok: matches #2 eval<C<17>> eC; // error: C does not match TT in #2 // because TT's first parameter is a type template parameter, // while 17 does not name a type eval<D<int, 17>> eD; // error: D does not match TT in #2 // because TT's second parameter is a type parameter pack, // while 17 does not name a type eval<E<int, float>> eE; // error: E does not match TT in #2 // because E's third (default) parameter is a non-type template<auto n> class D { /* ... */ }; // note: C++17 template<template<int> class R> class Z { /* ... */ }; Z<D> zd; // OK, auto -> int, P is at least as specialied as A template <int> struct SI { /* ... */ }; template <template<auto> class> void FA(); // note: C++17 FA<SI>(); // Error, int -> auto, P is not at least as specialied as A
-
Formally, a
template template-parameter P
isat least as specialized
as atemplate template argument A
if, given the following rewrite to two function templates, the function template corresponding to P is at least as specialized as the function template corresponding to A according to the partial ordering rules for function templates. Given an invented class template X with the template parameter list of A (including default arguments):- Each of the two function templates has the same template parameters, respectively, as P or A.
- Each function template has a single function parameter whose type is a specialization of X with template arguments corresponding to the template parameters from the respective function template where, for each template parameter PP in the template parameter list of the function template, a corresponding template argument AA is formed. If PP declares a parameter pack, then AA is the pack expansion PP...; otherwise, AA is the id-expression PP.
- If the rewrite produces an invalid type, then P is not at least as specialized as A.
-
Defaults can be specified for any kind of template parameter (type, non-type, or template), but not to parameter packs.
-
Default parameters are not allowed:
- in the
out-of-class definition
of a member of a class template (they have to be provided in the declaration inside the class body). Note that member templates of non-template classes can use default parameters in their out-of-class definitions - in
friend template
declarations, both class and function
- in the
-
On a
friend function template
declaration, default template arguments are allowed only if thedeclaration is a definition
, and no other declarations of this function appear in this translation unit. (C++11) -
Default template arguments that appear in the
declarations
and thedefinition
aremerged
similarly to default function arguments:template<typename T1, typename T2 = int> class A; template<typename T1 = int, typename T2> class A; // the above is the same as the following: template<typename T1 = int, typename T2 = int> class A;
- But the same parameter cannot be given default arguments twice in the same scope
-
The template parameter lists of
template template parameters
can have their owndefault
arguments, which are only in effect where the template template parameter itself is in scope:// class template, with a type template parameter with a default template<typename T = float> struct B {}; // template template parameter T has a parameter list, which // consists of one type template parameter with a default template<template<typename = float> typename T> struct A { void f(); void g(); }; // out-of-body member function template definitions template<template<typename TT> class T> void A<T>::f() { T<> t; // error: TT has no default in scope } template<template<typename TT = char> class T> void A<T>::g() { T<> t; // ok: t is T<char> }
-
Member access
for the names used in a default template parameter ischecked at the declaration
, not at the point of use:class B {}; template<typename T> class C { protected: typedef T TT; }; template<typename U, typename V = typename U::TT> class D: public U {}; D<C<B>>* d; // error: C::TT is protected
-
The
default
template argument is implicitlyinstantiated
when the value of that default argument is needed, except if the template is used to name a function: (C++14)template<typename T, typename U = int> struct S { }; S<bool>* p; // The default argument for U is instantiated at this point // the type of p is S<bool, int>*
- Template argument equivalence is used to determine whether two template-ids are same.
- Syntax
template <parameter-list > variable-declaration
- Syntax
template <parameter-list> function-declaration (1) template <parameter-list> requires constraint function-declaration (2) (C++20) function-declaration-with-placeholders (3) (C++20)
-
Syntax
template return-type name <argument-list> (parameter-list); // (1) template return-type name (parameter-list); // (2) with template argument deduction extern template return-type name <argument-list> (parameter-list); // (3) (C++11) extern template return-type name (parameter-list); // (4) (C++11)
-
A
trailing template-argument
can be left unspecified in an explicit instantiation of a function template specialization or of a member function template specialization if it can be deduced from the function parametertemplate<typename T, typename U> void f(T s, U s) { std::cout << s << " " << u << '\n'; } template void f<double>(double, bool); // instantiates f<double, bool>(double, bool) template void f<>(char, char); // instantiates f<char, char>(char, char), template argument deduced template void f(int, float); // instantiates f<int, float>(int, float), template argument deduced
-
Explicit instantiation of a function template or of a member function of a class template cannot use
inline
orconstexpr
. If the declaration of the explicit instantiation names an implicitly-declared special member function, the program is ill-formed. -
Explicit instantiation of a
constructor
cannot use a template parameter list (syntax (1)), which is also never necessary because they can be deduced (syntax (2)). -
Explicit instantiation of a
prospective destructor
must name the selected destructor of the class. (C++20) -
Explicit instantiation declarations do
not suppress
theimplicit instantiation
ofinline functions
,auto-declarations
,references
, andclass template specializations
. (thus, when the inline function that is a subject of explicit instantiation declaration is ODR-used, it is implicitly instantiated for inlining, but its out-of-line copy is not generated in this translation unit) -
Explicit instantiation definition of a function template with
default arguments
is not a use of the arguments, and does not attempt to initialize them:char* p = 0; template<class T> T g(T x = &p) { return x; } template int g<int>(int); // OK even though &p isn’t an int.
- When code refers to a function in context that requires the function definition to exist, [or if the existence of the definition affects the semantics of the program (C++11)], and this particular function has not been explicitly instantiated, implicit instantiation occurs. The list of template arguments does not have to be supplied if it can be deduced from context.
- Note: omitting <> entirely allows overload resolution to examine both template and non-template overloads.
template<typename T> void f(T s) {
std::cout << s << '\n';
}
f<double>(1); // instantiates and calls f<double>(double)
f<>('a'); // instantiates and calls f<char>(char)
f(7); // instantiates and calls f<int>(int)
void (*ptr)(std::string) = f; // instantiates f<string>(string)
- Template arguments of a function template may be obtained from
- template argument
deduction
default
template argumentsspecified
explicitly, which can be done in the following contexts:- in a function call expression
- when an address of a function is taken
- when a reference to function is initialized
- when a pointer to member function is formed
- in an explicit specialization
- in an explicit instantiation
- in a friend declaration
- template argument
- There is no way to explicitly specify template arguments to
overloaded operators
,conversion functions
, andconstructors
, because they are called without the use of the function name. - The specified
non-type arguments
must eithermatch
the types of the corresponding non-type template parameters, orbe convertible
to them. - The function
parameters
thatdo not participate
in template argument deduction (e.g. if the corresponding template arguments are explicitly specified)are subject
toimplicit conversions
to the type of the corresponding function parameter (as in the usual overload resolution). - A template
parameter pack
that is explicitly specified may be extended by template argument deduction if there are additional arguments:template<class ... Types> void f(Types ... values); void g() { f<int*, float*>(0, 0, 0); // Types = {int*, float*, int} }
-
This mechanism makes it possible to use
template operators
, since there is no syntax to specify template arguments for an operator other than by re-writing it as a function call expression -
Template argument deduction takes place after the function template name lookup (which may involve argument-dependent lookup) and before template argument substitution (which may involve SFINAE) and overload resolution.
-
Template argument deduction is also performed when the name of a class template is used as the type of an object being constructed: (C++17)
std::pair p(2, 4.5); std::tuple t(4, 3, 2.5); std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); auto lck = std::lock_guard(foo.mtx); std::lock_guard lck2(foo.mtx, ul);
- Template argument deduction for class templates takes place in declarations and in explicit cast expressions; see class template argument deduction for details.
-
Before deduction begins, the following Parameters-Arguments-Adjustment to P and A are made:
- If P is
not a reference type
:- if A is an
array type
, A is replaced by thepointer type
obtained from array-to-pointer conversion; - otherwise, if A is a
function type
, A is replaced by the pointer type obtained from function-to-pointer conversion; - otherwise, if A is a
cv-qualified type
, the top-level cv-qualifiers are ignored for deduction:
- if A is an
- If P is a
cv-qualified type
, the top-level cv-qualifiers are ignored for deduction. - If P is a
reference type
, the type referred to by P is used for deduction, ignore the reference. - If P is an
rvalue reference
to a cv-unqualified template parameter (so-calledforwarding reference
), and the corresponding function call argument is an lvalue, the type lvalue reference to A is used in place of A for deduction (Reference Collapse)- Note: in class template argument deduction, template parameter of a class template is never a forwarding reference (C++17)
template<class T> int f(T&&); // P is an rvalue reference to cv-unqualified T (forwarding reference) template<class T> int g(const T&&); // P is an rvalue reference to cv-qualified T (not special) int main() { int i; int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case) int n2 = f(0); // argument is not lvalue: calls f<int>(int&&) // int n3 = g(i); // error: deduces to g<int>(const int&&), which // cannot bind an rvalue reference to an lvalue }
- If P is
- Template argument deduction attempts to determine template arguments (types for type template parameters Ti, templates for template template parameters TTi, and values for non-type template parameters Ii), which can be substituted into each parameter P to produce the type deduced A, which is the same as the type of the argument A (transformed A), after adjustments listed above.
-
If removing references and cv-qualifiers from P gives
std::initializer_list<P'>
and A is a braced-init-list, then deduction is performed for every element of the initializer list, taking P' as the parameter and the list element A' as the argument (C++11) -
If removing references and cv-qualifiers from P gives
P'[N]
, and A is a non-empty braced-init-list, then deduction is performed as above, except if N is a non-type template parameter, it is deduced from thelength
of the initializer list: (C++11)template<class T, int N> void h(T const(&)[N]); h({1, 2, 3}); // deduced T = int, deduced N = 3 template<class T> void j(T const(&)[3]); j({42}); // deduced T = int, array bound is not a parameter, not considered struct Aggr { int i; int j; }; template<int N> void k(Aggr const(&)[N]); k({1, 2, 3}); // error: deduction fails, no conversion from int to Aggr k({{1}, {2}, {3}}); // OK: deduced N = 3 template<int M, int N> void m(int const(&)[M][N]); m({{1, 2}, {3, 4}}); // deduced M = 2, deduced N = 2 template<class T, int N> void n(T const(&)[N], T); n({{1}, {2}, {3}}, Aggr()); // deduced T = Aggr, deduced N = 3
-
If a
parameter pack
appears as the last P, then the type P is matched against the type A of each remaining argument of the call. Each match deduces the template arguments for the next position in the pack expansion: (C++11)template<class... Types> void f(Types&...); void h(int x, float& y) { const int z = x; f(x, y, z); // P = Types&..., A1 = x: deduced first member of Types... = int // P = Types&..., A2 = y: deduced second member of Types... = float // P = Types&..., A3 = z: deduced third member of Types... = const int // calls f<int, float, const int> }
-
If P is a
function type
, pointer to function type, or pointer to member function type and if A is a set of overloaded functions not containing function templates, template argumentdeduction
is attempted witheach overload
. If only one succeeds, that successful deduction is used. If none or more than one succeeds, the template parameter is non-deduced context (see below):template<class T> int f(T(*p)(T)); int g(int); int g(char); f(g); // P = T(*)(T), A = overload set // P = T(*)(T), A1 = int(int): deduced T = int // P = T(*)(T), A2 = int(char): fails to deduce T // only one overload works, deduction succeeds
-
After these transformations, the deduction processes as described below (cf. section
Deduction from type
) and attempts to find such template arguments that would make the deduced A (that is, P after adjustments listed above and the substitution of the deduced template parameters)identical
to the transformed A (that is, A after the adjustments listed above). -
If the usual deduction from P and A fails, the following alternatives are additionally considered:
-
If P is a reference type, the
deduced A
(i.e., the type referred to by the reference)can be more cv-qualified
than thetransformed A
:template<typename T> void f(const T& t); bool a = false; f(a); // P = const T&, adjusted to const T, A = bool: // deduced T = bool, deduced A = const bool // deduced A is more cv-qualified than A
-
The
transformed A
can be another pointer or pointer to member type thatcan be converted
to thededuced A
via a qualification conversions [or a function pointer conversion (C++17)]:template<typename T> void f(const T*); int* p; f(p); // P = const T*, A = int*: // deduced T = int, deduced A = const int* // qualification conversion applies (from int* to const int*)
-
If P is a class and P has the form simple-template-id, then the
transformed A
can be a derived class
of thededuced A
. Likewise, if P is a pointer to a class of the form simple-template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A:template<class T> struct B { }; template<class T> struct D : public B<T> { }; template<class T> void f(B<T>&) { } void f() { D<int> d; f(d); // P = B<T>&, adjusted to P = B<T> (a simple-template-id), A = D<int>: // deduced T = int, deduced A = B<int> // A is derived from deduced A }
-
-
Given a function parameter P that depends on one or more type template parameters Ti, template template parameters TTi, or non-type template parameters Ii, and the corresponding argument A, deduction takes place if P has one of the following forms:
T cv-list T T* T& T&& T[integer-constant] class-template-name<T> type(T) T() T(T) T type::* type T::* T T::* T(type::*)() type(T::*)() type(type::*)(T) type(T::*)(T) T (type::*)(T) T (T::*)() T (T::*)(T) type[i] class-template-name<I> TT<T> TT<I> TT<>
-
If P has one of the forms that include a
template parameter list <T> or <I>
, then each element Pi of that template argument list is matched against the correspondingtemplate argument
Ai of its A. If the last Pi is a pack expansion, then its pattern is compared against each remaining argument in the template argument list of A. A trailing parameter pack that is not otherwise deduced, is deduced to an empty parameter pack. -
If P has one of the forms that include a
function parameter list (T)
, then each parameter Pi from that list is compared with the corresponding argument Ai from A's function parameter list. If the last Pi is a pack expansion, then its declarator is compared with each remaining Ai in the parameter type list of A. -
When the value of the argument corresponding to a
non-type template parameter
P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value. (C++17)template <long n> struct A { }; template <class T> struct C; template <class T, T n> struct C<A<n>> { using Q = T; }; typedef long R; typedef C<A<2>>::Q R; // OK: T was deduced to long // from the template argument value in the type A<2> template <auto X> class bar{}; template <class T, T n> void f(bar<n> x); f(bar<3>{}); // OK: T was deduced to int (and n to 3) // from the template argument value in the type bar<3>
-
The type of N in the type T[N] is std::size_t
template<class T, T i> void f(int (&a)[i]); int v[10]; f(v); // OK: T is std::size_t
-
If a
non-type template parameter
is used in the parameter list, and the corresponding template argument is deduced, the type of thededuced template argument
(as specified in its enclosing template parameter list, meaning references are preserved) mustmatch
the type of the non-type template parameterexactly
, except that cv-qualifiers are dropped, and except where the template argument is deduced from an array bound—in that case any integral type is allowed, even bool though it would always become true:template<int i> class A { }; template<short s> void f(A<s>); // the type of the non-type template param is short void k1() { A<1> a; // the type of the non-type template param of a is int f(a); // P = A<(short)s>, A = A<(int)1> // error: deduced non-type template argument does not have the same // type as its corresponding template argument f<1>(a); // OK: the template argument is not deduced, // this calls f<(short)1>(A<(short)1>) } template<int&> struct X; template<int& R> void k2(X<R>&); int n; void g(X<n> &x) { k2(x); // P = X<R>, A = X<n> // parameter type is int& // argument type is int& in struct X's template declaration // OK (with CWG 2091): deduces R to refer to n }
-
Type template parameter
cannot be deduced
from the type of afunction default argument
:template<typename T> void f(T = 5, T = 7); void g() { f(1); // OK: calls f<int>(1, 7) f(); // error: cannot deduce T f<int>(); // OK: calls f<int>(5, 7) }
-
Deduction of
template template parameter
can use the type used in the template specialization used in the function call:template<template<typename> class X> struct A { }; // A is a template with a TT param template<template<typename> class TT> void f(A<TT>) { } template<class T> struct B { }; A<B> ab; f(ab); // P = A<TT>, A = A<B>: deduced TT = B, calls f(A<B>)
- The parameter P is obtained as follows:
- in T, the declared type of the variable that includes auto, every occurrence of auto is replaced with an imaginary type template parameter U or,
- if the initialization is copy-list-initialization, with
std::initializer_list<U>
. The argument A is the initializer expression. - After deduction of U from P and A following the rules described above, the deduced U is substituted into P to get the actual variable type:
const auto& x = 1 + 2; // P = const U&, A = 1 + 2: // same rules as for calling f(1 + 2) where f is // template<class U> void f(const U& u) // deduced U = int, the type of x is const int& auto l = {13}; // P = std::initializer_list<U>, A = {13}: // deduced U = int, the type of l is std::initializer_list<int>
- In
direct-list-initialization
(but not in copy-list-initalization), when deducing the meaning of the auto from a braced-init-list, the braced-init-list must containonly one element
, and the type of auto will be the type of that element:auto x1 = {3}; // x1 is std::initializer_list<int> auto x2{1, 2}; // error: not a single element auto x3{3}; // x3 is int // (before N3922 x2 and x3 were both std::initializer_list<int>)
-
For auto-returning functions, the parameter P is obtained as follows:
- in T, the declared return type of the function that includes auto, every occurrence of auto is replaced with an imaginary type template parameter U.
- The argument A is the expression of the return statement, and if the return statement has no operand, A is void().
- After deduction of U from P and A following the rules described above, the deduced U is substituted into T to get the actual return type:
auto f() { return 42; } // P = auto, A = 42: // deduced U = int, the return type of f is int
-
If such function has
multiple return statements
, the deduction is performed for each return statement. All the resulting typesmust be the same
and become the actual return type. -
If such function has
no return statement
, A isvoid()
when deducing. -
Note: the meaning of
decltype(auto)
placeholder in variable and function declarations does not use template argument deduction.
- Template argument deduction is used during overload resolution, when generating specializations from a candidate template function. P and A are the same as in a regular function call
std::string s; std::getline(std::cin, s); // "std::getline" names 4 function templates, // 2 of which are candidate functions (correct number of parameters) // 1st candidate template: // P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // deduction determines the type template parameters CharT, Traits, and Allocator // specialization std::getline<char, std::char_traits<char>, std::allocator<char>> // 2nd candidate template: // P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // deduction determines the type template parameters CharT, Traits, and Allocator // specialization std::getline<char, std::char_traits<char>, std::allocator<char>> // overload resolution ranks reference binding from lvalue std::cin and picks // the first of the two candidate specializations
- Template argument deduction is used when taking an address of a overload set, which includes function templates. The function type of the function template is P. The target type is the type of A
std::cout << std::endl; // std::endl names a function template // type of endl P = std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&) // operator<< parameter A = std::basic_ostream<char, std::char_traits<char>>& (*)( // std::basic_ostream<char, std::char_traits<char>>& // ) // (other overloads of operator<< are not viable) // deduction determines the type template parameters CharT and Traits
- An additional rule is applied to the deduction in this case: when comparing function parameters Pi and Ai, if any Pi is an rvalue reference to cv-unqualified template parameter (a
forwarding reference
) and the corresponding Ai is anlvalue reference
, then Pi is adjusted to the template parameter type (T&& becomes T). - If the return type of the function template is a placeholder (auto or decltype(auto)), that return type is a non-deduced context and is determined from the instantiation. (C++14)
- When the same function template specialization matches more than one overloaded function template (this often results from template argument deduction), partial ordering of overloaded function templates is performed to select the best match.
-
Template argument deduction is used when selecting user-defined conversion function template arguments.
-
Paramters-Arguments-Adjustments
- A is the type that is required as the result of the conversion. P is the return type of the conversion function template, except that
- a) if the return type is a
reference type
then P is the referred type; - b) if the return type is an
array type
and A is not a reference type, then P is the pointer type obtained by array-to-pointer conversion; - c) if the return type is a
function type
and A is not a reference type, then P is the function pointer type obtained by function-to-pointer conversion; - d) if P is
cv-qualified
, the top-level cv-qualifiers are ignored.
- a) if the return type is a
- If A is
cv-qualified
, the top-level cv-qualifiers are ignored. If A is a reference type, the referred type is used by deduction.
- A is the type that is required as the result of the conversion. P is the return type of the conversion function template, except that
-
If the usual deduction from P and A (as described above) fails, the following alternatives are additionally considered:
- a) if A is a reference type, A
can be more cv-qualified
than the deduced A; - b) if A is a pointer or pointer to member type, the deduced A is allowed to be any pointer that
can be converted
to A by qualification conversion:struct A { template<class T> operator T***(); }; A a; const int* const* const* p1 = a; // P = T***, A = const int* const* const* // regular function-call deduction for // template<class T> void f(T*** p) as if called with the argument // of type const int* const* const* fails // additional deduction for conversion functions determines T = int // (deduced A is int***, convertible to const int* const* const*)
- c) if A is a
function pointer type
, the deduced A is allowed to be pointer to noexcept function, convertible to A by function pointer conversion; (C++17) - d) if A is a
pointer to member function
, the deduced A is allowed to be a pointer to noexcept member function, convertible to A by function pointer conversion. (C++17)
- a) if A is a reference type, A
- Template argument deduction is used in
explicit instantiations
,explicit (full) specializations
, and thosefriend declarations
where the declarator-id happens to refer to a specialization of a function template (for example, friend ostream& operator<< <> (...)), if not all template arguments are explicitly specified or defaulted, template argument deduction is used to determine which template's specialization is referred to. - P is the type of the function template that is being considered as a potential match, and A is the function type from the declaration. If there are no matches or more than one match (after partial ordering), the function declaration is ill-formed:
template<class X> void f(X a); // 1st template f template<class X> void f(X* a); // 2nd template f template<> void f<>(int* a) { } // explicit specialization of f // P1 = void(X), A1 = void(int*): deduced X = int*, f<int*>(int*) // P2 = void(X*), A2 = void(int*): deduced X = int, f<int>(int*) // f<int*>(int*) and f<int>(int*) are then submitted to partial ordering // which selects f<int>(int*) as the more specialized template
- An additional rule is applied to the deduction in this case: when comparing function parameters Pi and Ai, if any Pi is an rvalue reference to cv-unqualified template parameter (a
forwarding reference
) and the corresponding Ai is an lvalue reference, then Pi is adjusted to the template parameter type (T&& becomes T
).
- Template argument deduction is used when determining if a deallocation function template specialization matches a given placement form of operator new.
- P is the type of the function template that is being considered as a potential match, and A is the function type of the deallocation function that would be the match for the placement operator new under consideration. If there is no match or more than one match (after overload resolution), the placement deallocation function is not called (memory leak may occur):
struct X { X() { throw std::runtime_error(""); } static void* operator new(std::size_t sz, bool b) { return ::operator new(sz); } static void* operator new(std::size_t sz, double f) { return ::operator new(sz); } template<typename T> static void operator delete(void* ptr, T arg) { ::operator delete(ptr); } }; int main() { try { X* p1 = new (true) X; // when X() throws, operator delete is looked up // P1 = void(void*, T), A1 = void(void*, bool): deduced T = bool // P2 = void(void*, T), A2 = void(void*, double): deduced T = double // overload resolution picks operator delete<bool> } catch(const std::exception&) { } try { X* p1 = new (13.2) X; // same lookup, picks operator delete<double> } catch(const std::exception&) { } }
-
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
-
However, if deduction succeeds for all parameters that participate in template argument deduction, and all template arguments that aren't deduced are explicitly specified or defaulted, then the remaining function parameters are compared with the corresponding function arguments. For each remaining parameter P with a type that was non-dependent before substitution of any explicitly-specified template arguments, if the corresponding argument A cannot be implicitly converted to P, deduction fails.
-
Parameters with dependent types in which no template-parameters participate in template argument deduction, and parameters that became non-dependent due to substitution of explicitly-specified template arguments will be checked during overload resolution:
template<class T> struct Z { typedef typename T::x xx; }; template<class T> typename Z<T>::xx f(void*, T); // #1 template<class T> void f(int, T); // #2 struct A { } a; f(1, a); // for #1, deduction determines T = struct A, but the remaining argument 1 // cannot be implicitly converted to its parameter void*: deduction fails // instantiation of the return type is not requested // for #2, deduction determines T = struct A, and the remaining argument 1 // can be implicitly converted to its parameter int: deduction succeeds // the function call compiles as a call to #2 (deduction failure is SFINAE)
- Alias templates are never deduced:
template<class T> struct Alloc { }; template<class T> using Vec = vector<T, Alloc<T>>; Vec<int> v; template<template<class, class> class TT> void g(TT<int, Alloc<int>>); g(v); // OK: deduced TT = vector template<template<class> class TT> void f(TT<int>); f(v); // error: TT cannot be deduced as "Vec" because Vec is an alias template
-
- The
nested-name-specifier
(everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:
// the identity template, often used to exclude specific arguments from deduction // (available as std::type_identity as of C++20) template<typename T> struct identity { typedef T type; }; template<typename T> void bad(std::vector<T> x, T value = 1); template<typename T> void good(std::vector<T> x, typename identity<T>::type value = 1); std::vector<std::complex<double>> x; bad(x, 1.2);// P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: deduced T = std::complex<double> // P2 = T, A2 = double // P2/A2: deduced T = double // error: deduction fails, T is ambiguous good(x, 1.2);// P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: deduced T = std::complex<double> // P2 = identity<T>::type, A2 = double // P2/A2: uses T deduced by P1/A1 because T is to the left of :: in P2 // OK: T = std::complex<double>
- The
-
- The expression of a
decltype
-specifier: (C++11)
template<typename T> void f(decltype(*std::declval<T>()) arg); int n; f<int*>(n); // P = decltype(*declval<T>()), A = int: T is in non-deduced context
- The expression of a
-
- A
non-type template argument
or an array bound in which a subexpression references a template parameter:
template<std::size_t N> void f(std::array<int, 2 * N> a); std::array<int, 10> a; f(a); // P = std::array<int, 2 * N>, A = std::array<int, 10>: // 2 * N is non-deduced context, N cannot be deduced // note: f(std::array<int, N> a) would be able to deduce N
- A
-
- A template parameter used in the parameter type of a function parameter that has a
default argument
that is being used in the call for which argument deduction is being done:
template<typename T, typename F> void f(const std::vector<T>& v, const F& comp = std::less<T>()); std::vector<std::string> v(3); f(v); // P1 = const std::vector<T>&, A1 = std::vector<std::string> lvalue // P1/A1 deduced T = std::string // P2 = const F&, A2 = std::less<std::string> rvalue // P2 is non-deduced context for F (template parameter) used in the // parameter type (const F&) of the function parameter comp, // that has a default argument that is being used in the call f(v)
- A template parameter used in the parameter type of a function parameter that has a
-
- The parameter P, whose A is a
function
or aset of overloads
such that more than one function matches P or no function matches P or the set of overloads includes one or more function templates:
template<typename T> void out(const T& value) { std::cout << value; } out("123"); // P = const T&, A = const char[4] lvalue: deduced T = char[4] out(std::endl); // P = const T&, A = function template: T is in non-deduced context
- The parameter P, whose A is a
-
- The parameter P, whose
A is a braced-init-list
, butP is not std::initializer_list
, a reference to one (possibly cv-qualified), or a reference to an array: (c++11)
template<class T> void g1(std::vector<T>); template<class T> void g2(std::vector<T>, T x); g1({1, 2, 3}); // P = std::vector<T>, A = {1, 2, 3}: T is in non-deduced context // error: T is not explicitly specified or deduced from another P/A g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T is in non-deduced context // P2 = T, A2 = int: deduced T = int
- The parameter P, whose
-
- The parameter P which is a
parameter pack
and does not occur at the end of the parameter list:
template<class... Ts, class T> void f1(T n, Ts... args); template<class... Ts, class T> void f2(Ts... args, T n); f1(1, 2, 3, 4); // P1 = T, A1 = 1: deduced T = int // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: deduced Ts = [int, int, int] f2(1, 2, 3, 4); // P1 = Ts...: Ts is non-deduced context
- The parameter P which is a
-
- The template parameter list that appears within the parameter P, and which includes a
pack expansion
that is not at the very end of the template parameter list:
template<int...> struct T { }; template<int... Ts1, int N, int... Ts2> void good(const T<N, Ts1...>& arg1, const T<N, Ts2...>&); template<int... Ts1, int N, int... Ts2> void bad(const T<Ts1..., N>& arg1, const T<Ts2..., N>&); T<1, 2> t1; T<1, -1, 0> t2; good(t1, t2); // P1 = const T<N, Ts1...>&, A1 = T<1, 2>: // deduced N = 1, deduced Ts1 = [2] // P2 = const T<N, Ts2...>&, A2 = T<1, -1, 0>: // deduced N = 1, deduced Ts2 = [-1, 0] bad(t1, t2); // P1 = const T<Ts1..., N>&, A1 = T<1, 2>: // <Ts1..., N> is non-deduced context // P2 = const T<Ts2..., N>&, A2 = T<1, -1, 0>: // <Ts2..., N> is non-deduced context
- The template parameter list that appears within the parameter P, and which includes a
-
- For P of
array type
(but not reference to array or pointer to array), themajor array bound
:
template<int i> void f1(int a[10][i]); template<int i> void f2(int a[i][20]); // P = int[i][20], array type template<int i> void f3(int (&a)[i][20]); // P = int(&)[i][20], reference to array int a[10][20]; f1(a); // OK: deduced i = 20 f1<20>(a); // OK f2(a); // error: i is non-deduced context f2<10>(a); // OK f3(a); // OK: deduced i = 10 f3<10>(a); // OK
- For P of
-
Refer Function template argument deduction for more details.
- When all template arguments have been specified, deduced or obtained from default template arguments, every use of a template parameter in the function parameter list is replaced with the corresponding template arguments.
- After substitution, all
function parameters
ofarray
andfunction type
are adjusted to pointers and alltop-level cv-qualifiers
are dropped from function parameters (as in a regular function declaration). - The removal of the top-level cv-qualifiers does not affect the type of the parameter as it appears within the function:
template <class T> void f(T t); template <class X> void g(const X x); template <class Z> void h(Z z, Z* zp); // two different functions with the same type, but // within the function, t has different cv qualifications f<int>(1); // function type is void(int), t is int f<const int>(1); // function type is void(int), t is const int // two different functions with the same type and the same x // (pointers to these two functions are not equal, // and function-local statics would have different addresses) g<int>(1); // function type is void(int), x is const int g<const int>(1); // function type is void(int), x is const int // only top-level cv-qualifiers are dropped: h<const int>(1, NULL); // function type is void(int, const int*) // z is const int, zp is const int*
-
Function distinctions:
- A
non-template function
is always distinct from atemplate specialization
with the same type. - Specializations of different function templates are always distinct from each other even if they have the same type.
- Two function templates with the same return type and the same parameter list are distinct and can be distinguished with explicit template argument list.
- A
-
When an expression that uses type or non-type template parameters appears in the function parameter list or in the return type, that expression remains a part of the function template signature for the purpose of overloading:
template<int I, int J> A<I+J> f(A<I>, A<J>); // overload #1 template<int K, int L> A<K+L> f(A<K>, A<L>); // same as #1 template<int I, int J> A<I-J> f(A<I>, A<J>); // overload #2
-
Equivalence
- Two expressions involving template parameters are called equivalent if two function definitions that contain these expressions would be the same under ODR rules, that is, the two expressions contain the same sequence of tokens whose names are resolved to same entities via name lookup, except template parameters may be differently named.
template <int I, int J> void f(A<I+J>); // template overload #1 template <int K, int L> void f(A<K+L>); // equivalent to #1
- Two lambda expressions are never equivalent. (C++20)
- When determining if two dependent expressions are equivalent, only the
dependent names
involved are considered, not the results of name lookup. If multiple declarations of the same template differ in the result of name lookup, the first such declaration is used:template <class T> decltype(g(T())) h(); // decltype(g(T())) is a dependent type int g(int); template <class T> decltype(g(T())) h() { // redeclaration of h() uses earlier lookup return g(T()); // ...although the lookup here does find g(int) } int i = h<int>(); // template argument substitution fails; g(int) // was not in scope at the first declaration of h()
- Two expressions involving template parameters are called functionally equivalent if they are not equivalent, but for any given set of template arguments, the evaluation of the two expressions results in the same value.
- Two function templates are considered functionally equivalent if they are equivalent, except that one or more expressions that involve template parameters in their return types and parameter lists are functionally equivalent.
- If a program contains declarations of function templates that are functionally equivalent but not equivalent, the program is ill-formed; no diagnostic is required.
// equivalent template <int I> void f(A<I>, A<I+10>); // overload #1 template <int I> void f(A<I>, A<I+10>); // redeclaration of overload #1 // not equivalent template <int I> void f(A<I>, A<I+10>); // overload #1 template <int I> void f(A<I>, A<I+11>); // overload #2 // functionally-equivalent but not equivalent // This program is ill-formed, no diagnostic required template <int I> void f(A<I>, A<I+10>); // overload #1 template <int I> void f(A<I>, A<I+1+2+3+4>); // functionally equivalent
- Two expressions involving template parameters are called equivalent if two function definitions that contain these expressions would be the same under ODR rules, that is, the two expressions contain the same sequence of tokens whose names are resolved to same entities via name lookup, except template parameters may be differently named.
-
When the same function template specialization matches more than one overloaded function template (this often results from template argument deduction), partial ordering of overloaded function templates is performed to select the best match. The ordering is partial because there can be some templates that are considered equally specialized.
-
Partial ordering happens in:
-
- overload resolution for a
call to a function
template specialization
- overload resolution for a
-
- when the
address of a function
template specialization is taken
- when the
-
- when a
placement operator delete
that is a function template specialization is selected to match a placement operator new
- when a
-
- when a
friend function declaration
, anexplicit instantiation
, or anexplicit specialization
refers to a function template specialization
template<class X> void f(X a); // first template f template<class X> void f(X* a); // second template f template<> void f<>(int *a) {} // explicit specialization // template argument deduction comes up with two candidates: // f<int*>(int*) and f<int>(int*) // partial ordering selects f<int>(int*) as more specialized
- when a
-
-
To determine which of any two function templates is more specialized,
partial ordering work flow
:-
transforms
one of the two templates as follows:- For each type, non-type, and template parameter, including parameter packs, a
unique fictitious type
, value, or template is generated and substituted into function type of the template - If only one of the two function templates being compared is a
member function
, and that function template is a non-static member of some class A, a new first parameter is inserted into its parameter list, whose type is cv A&& if the member function template is &&-qualified and cv A& otherwise (cv is the cv-qualification of the member function template) -- this helps the ordering of operators, which are looked up both as member and as non-member functions:struct A {}; template<class T> struct B { template<class R> int operator*(R&); // #1 }; template<class T, class R> int operator*(T&, R&); // #2 int main() { A a; B<A> b; b * a; // template argument deduction for int B<A>::operator*(R&) gives R=A // for int operator*(T&, R&), T=B<A>, R=A // For the purpose of partial ordering, the member template B<A>::operator* // is transformed into template<class R> int operator*(B<A>&, R&); // partial ordering between // int operator*( T&, R&) T=B<A>, R=A // and int operator*(B<A>&, R&) R=A // selects int operator*(B<A>&, A&) as more specialized }
- For each type, non-type, and template parameter, including parameter packs, a
-
After one of the two templates was transformed as described above,
template argument deduction
is executed using the transformed template as theargument template
and the original template type of the other template as theparameter template
. The process is then repeated using the second template (after transformations) as the argument and the first template in its original form as the parameter. -
The types used to determine the order depend on the context:
- in the context of a
function call
, the types are thosefunction parameter types
for which the function call has arguments (default function arguments, parameter packs, and ellipsis parameters are not considered -- see examples below) - in the context of
a call to a user-defined conversion function
, thereturn types
of the conversion function templates are used - in
other contexts
, thefunction template type
is used
- in the context of a
-
(
Parameters-Arguments Adjustment
) Each type from the list above from the parameter template is deduced. Before deduction begins, each parameter P of the parameter template and the corresponding argument A of the argument template is adjusted as follows:- If both P and A are
reference types
before, determine which ismore cv-qualified
(in all other cases, cv-qualificiations are ignored for partial ordering purposes) - If
P/A is a reference
type, it is replaced by the type referred to,ignore reference
- If
P/A is cv-qualified
, P/A is replaced with cv-unqualified version of itself,ignore cv-qualifier
- If both P and A are
-
After these adjustments, deduction of P from A is done following
template argument deduction from a type
.
-
-
More specialized
-
If the argument A of the transformed template-1
can be used to deduce
the corresponding parameter P of template-2, but not vice versa, then this A is more specialized than P with regards to the type(s) that are deduced by this P/A pair. -
If deduction succeeds in both directions, and the original P and A were reference types, then additional tests are made:
- If A was
lvalue reference
and P wasrvalue reference
, A is considered to be more specialized than P - If A was
more cv-qualified
than P, A is considered to be more specialized than P
- If A was
-
In all other cases, neither template is more specialized than the other with regards to the type(s) deduced by this P/A pair.
-
After considering every P and A in both directions, if, for each type that was considered,
- template-1 is at least as specialized as template-2 for all types
- template-1 is more specialized than template-2 for some types
- template-2 is not more specialized than template-1 for any types OR is not at least as specialized for any types
- Then template-1 is more specialized than template-2
-
-
E.g.,
template<class T> void f(T); // template #1 template<class T> void f(T*); // template #2 template<class T> void f(const T*); // template #3 const int* p; f(p); // overload resolution picks: #1: void f(T ) [T = const int *] // #2: void f(T*) [T = const int] // #3: void f(const T *) [T = int] // partial ordering // #1 from transformed #2: void(T) from void(U1*): P=T A=U1*: deduction ok: T=U1* // #2 from transformed #1: void(T*) from void(U1): P=T* A=U1: deduction fails // #2 is more specialized than #1 with regards to T // #1 from transformed #3: void(T) from void(const U1*): P=T, A=const U1*: ok // #3 from transformed #1: void(const T*) from void(U1): P=const T*, A=U1: fails // #3 is more specialized than #1 with regards to T // #2 from transformed #3: void(T*) from void(const U1*): P=T* A=const U1*: ok // #3 from transformed #2: void(const T*) from void(U1*): P=const T* A=U1*: fails // #3 is more specialized than #2 with regards to T // result: #3 is selected // in other words, f(const T*) is more specialized than f(T) or f(T*)
template<class T> void f(T, T*); // #1 template<class T> void f(T, int*); // #2 void m(int* p) { f(0, p); // deduction for #1: void f(T, T*) [T = int] // deduction for #2: void f(T, int*) [T = int] // partial ordering: // #1 from #2: void(T,T*) from void(U1,int*): P1=T, A1=U1: T=U1 // P2=T* A2=int*: T=int: fails // #2 from #1: void(T,int*) from void(U1,U2*): P1=T A1=U1: T=U1 // P2=int* A2=U2*: fails // neither is more specialized w.r.t T, the call is ambiguous }
template<class T> void g(T); // template #1 template<class T> void g(T&); // template #2 void m() { float x; g(x); // deduction from #1: void g(T ) [T = float] // deduction from #2: void g(T&) [T = float] // partial ordering // #1 from #2: void(T) from void(U1&): P=T, A=U1 (after adjustment), ok // #2 from #1: void(T&) from void(U1): P=T (after adjustment), A=U1: ok // neither is more specialized w.r.t T, the call is ambiguous }
template<class T> struct A { A(); }; template<class T> void h(const T&); // #1 template<class T> void h(A<T>&); // #2 void m() { A<int> z; h(z); // deduction from #1: void h(const T &) [T = A<int>] // deduction from #2: void h(A<T> &) [T = int] // partial ordering // #1 from #2: void(const T&) from void(A<U1>&): P=T A=A<U1> T=A<U1> ok // #2 from #1: void(A<T>&) from void(const U1&): P=A<T> A=const U1 fails // #2 is more specialized than #1 w.r.t T const A<int> z2; h(z2); // deduction from #1: void h(const T&) [T = A<int>] // deduction from #2: void h(A<T>&) [T = int], but substitution fails // only one overload to choose from, partial ordering not tried, #1 is called }
- Since in a call context considers only parameters for which there are explicit call arguments, those
function parameter packs
,ellipsis parameters
, andparameters with default arguments
, for which there is no explicit call argument, are ignored:
template<class T> void f(T); // #1 template<class T> void f(T*, int=1); // #2 void m(int* ip) { int* ip; f(ip); // calls #2 (T* is more specialized than T) }
template<class T> void g(T); // #1 template<class T> void g(T*, ...); // #2 void m(int* ip) { g(ip); // calls #2 (T* is more specialized than T) }
template<class T, class U> struct A { }; template<class T, class U> void f(U, A<U,T>* p = 0); // #1 template< class U> void f(U, A<U,U>* p = 0); // #2 void h() { f<int>(42, (A<int, int>*)0); // calls #2 f<int>(42); // error: ambiguous }
template<class T > void g(T, T = T()); // #1 template<class T, class... U> void g(T, U ...); // #2 void h() { g(42); // error: ambiguous }
template<class T, class... U> void f(T, U...); // #1 template<class T > void f(T); // #2 void h(int i) { f(&i); // calls #2 due to the tie-breaker between parameter pack and no parameter // (note: was ambiguous between DR692 and DR1395) }
template<class T, class... U> void g(T*, U...); // #1 template<class T > void g(T); // #2 void h(int i) { g(&i); // OK: calls #1 (T* is more specialized than T) }
template <class ...T> int f(T*...); // #1 template <class T> int f(const T&); // #2 f((int*)0); // OK: selects #1 // (was ambiguous before DR1395 because deduction failed in both directions)
template<class... Args> void f(Args... args); // #1 template<class T1, class... Args> void f(T1 a1, Args... args); // #2 template<class T1, class T2> void f(T1 a1, T2 a2); // #3 f(); // calls #1 f(1, 2, 3); // calls #2 f(1, 2); // calls #3; non-variadic template #3 is more // specialized than the variadic templates #1 and #2
- During template argument deduction within the partial ordering process, template parameters don't require to be matched with arguments, if the argument is not used in any of the types considered for partial ordering
template <class T> T f(int); // #1 template <class T, class U> T f(U); // #2 void g() { f<int>(1); // specialization of #1 is explicit: T f(int) [T = int] // specialization of #2 is deduced: T f(U) [T = int, U = int] // partial ordering (only considering the argument type) // #1 from #2: T(int) from U1(U2): fails // #2 from #1: T(U) from U1(int): ok: U=int, T unused // calls #1 }
- Partial ordering of function templates containing
template parameter packs
isindependent
of thenumber of deduced arguments
for those template parameter packs.
template<class...> struct Tuple { }; template< class... Types> void g(Tuple<Types ...>); // #1 template<class T1, class... Types> void g(Tuple<T1, Types ...>); // #2 template<class T1, class... Types> void g(Tuple<T1, Types& ...>); // #3 g(Tuple<>()); // calls #1 g(Tuple<int, float>()); // calls #2 g(Tuple<int, float&>()); // calls #3 [TODO:?] g(Tuple<int>()); // calls #3 [TODO:?]
- To compile a call to a function template, the compiler has to decide between
non-template overloads
,template overloads
, and thespecializations of the template overloads
.
template< class T > void f(T); // #1: template overload template< class T > void f(T*); // #2: template overload void f(double); // #3: non-template overload template<> void f(int); // #4: specialization of #1 f('a'); // calls #1 f(new int(1)); // calls #2 f(1.0); // calls #3 f(1); // calls #4
- Since in a call context considers only parameters for which there are explicit call arguments, those
- Note that only
non-template
andprimary template overloads
participate in overload resolution. The specializations are not overloads and are not considered. Only after the overload resolution selects the best-matching primary function template, its specializations are examined to see if one is a better match.
- When
placeholder types
(eitherauto
orConcept auto
) appear in the parameter list of a function declaration or of a function template declaration, the declaration declares a function template, and one invented template parameter for each placeholder is appended to the template parameter listvoid f1(auto); // same as template<class T> void f(T) void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&); template <class T, C U> void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z);
- Abbreviated function templates can be specialized like all function templates.
template<> void f4<int>(const int*, const double&); // specialization of f4<int, const double>
- Syntax
template <parameter-list> class-declaration
- A class template by itself is not a type, or an object, or any other entity. No code is generated from a source file that contains only template definitions. In order for any code to appear, a template must be instantiated: the template arguments must be provided so that the compiler can generate an actual class (or function, from a function template).
- Syntax
template class-key template-name <argument-list>; // Explicit instantiation definition extern template class-key template-name <argument-list>;// Explicit instantiation declaration (C++11)
- Classes, functions, variables, and member template specializations can be explicitly instantiated from their templates. Member functions, member classes, and static data members of class templates can be explicitly instantiated from their member definitions.
- An explicit instantiation declaration (an extern template) skips implicit instantiation step. This can be used to reduce compilation times by explicitly declaring a template instantiation in all but one of the source files using it, and explicitly defining it in the remaining file.
- Explicit instantiation can only appear in the enclosing namespace of the template, unless it uses qualified-id
- Explicit instantiation has no effect if an explicit specialization appeared before for the same set of template arguments
- Only the
declaration is required
to be visible whenexplicitly instantiating
afunction template
, avariable template
, amember function
orstatic data member
of a class template, or amember function template
. - The
complete definition
must appear before theexplicit instantiation
of aclass template
, amember class
of a class template, or amember class template
, unless an explicit specialization with the same template arguments appeared before. - When an explicit instantiation names a class template specialization, it serves as an explicit instantiation of the same kind (declaration or definition) of each of its
non-inherited non-template members
that has not been previously explicitly specialized in the translation unit. If this explicit instantiation is a definition, it is also an explicit instantiation definition only for the members that have been defined at this point. - Explicit instantiation definitions
ignore member access
specifiers: parameter types and return types may be private.
- When code refers to a template in context that requires a completely defined type, or when the completeness of the type affects the code, and this particular type has not been explicitly instantiated, implicit instantiation occurs.
- E.g., when an
object
of this type is constructed, butnot
when apointer
to this type is constructed. - This applies to the members of the class template: unless the member is used in the program, it is not instantiated, and does not require a definition.
template<class T>
struct Z { // template definition
void f() {}
void g(); // never defined
};
template struct Z<double>; // explicit instantiation of Z<double>
Z<int> a; // implicit instantiation of Z<int>
Z<char>* p; // nothing is instantiated here
p->f(); // implicit instantiation of Z<char> and Z<char>::f() occurs here.
// Z<char>::g() is never needed and never instantiated: it does not have to be defined
-
any declaration that specifies initialization of a variable and variable template
std::pair p(2, 4.5); // deduces to std::pair<int, double> p(2, 4.5); std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5); std::less l; // same as std::less<void> l;
-
new-expressions
template<class T> struct A { A(T,T); }; auto y = new A{1,2}; // allocated type is A<int>
-
function-style cast expressions
auto lck = std::lock_guard(mtx); // deduces to std::lock_guard<std::mutex> std::copy_n(vi1, 3, std::back_insert_iterator(vi2));// deduces to std::back_insert_iterator<T>, // where T is the type of the container vi2 std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // deduces to Foo<T>, where T is the unique lambda type
-
the type of a non-type template parameter (C++20)
template<class T> struct X { X(T) {} auto operator<=>(const X&) const = default; }; template<X x> struct Y { }; Y<0> y; // OK, Y<X<int>(0)>
-
When a
function-style cast
ordeclaration of a variable
uses the name of a primary class template C without an argument list as the type specifier, deduction will proceed as follows:- If C is defined, for each constructor (or constructor template) Ci declared in the named primary template (if it is defined), a
fictional function template
Fi, is constructed, such thattemplate parameters
of Fi are the template parameters of C followed (if Ci is a constructor template) by the template parameters of Ci (default template arguments are included too)- the
function parameters
of Fi are the constructor parameters - the
return type
of Fi is C followed by the template parameters of the class template enclosed in <>
template<typename T> struct Base { Base(T a, T b) {} }; template<T> Base<T> F(T a, T b); // fictional function template
- If C is not defined or does not declare any constructors, an additional fictional function template is added, derived as above from a
hypothetical constructor
C() - In any case, an additional fictional function template derived as above from a hypothetical constructor C(C) is added, called the
copy deduction candidate
.template<T> Base<T> F(Base<T>); // copy deduction candidate
- If C is defined, for each constructor (or constructor template) Ci declared in the named primary template (if it is defined), a
-
Template argument deduction
andoverload resolution
is then performed for initialization of a fictional object ofhypothetical class type
, whose constructor signatures match the guides (except for return type) for the purpose of forming an overload set, and the initializer is provided by the context in which class template argument deduction was performed, except that the first phase of list-initialization (considering initializer-list constructors) is omitted if the initializer list consists of a single expression of type (possibly cv-qualified) U, where U is a specialization of C or a class derived from a specialization of C. -
These fictional constructors are public members of the hypothetical class type. They are explicit if the guide was formed from an explicit constructor. If overload resolution fails, the program is ill-formed. Otherwise, the return type of the selected F template specialization becomes the deduced class template specialization.
template<class T> struct UniquePtr { UniquePtr(T* t); }; UniquePtr dp{new auto(2.0)}; // One declared constructor: // C1: UniquePtr(T*); // Set of implicitly-generated deduction guides: // F1: template<class T> UniquePtr<T> F(T *p); // F2: template<class T> UniquePtr<T> F(UniquePtr<T>); // copy deduction candidate // imaginary class to initialize: // struct X { // template<class T> X(T *p); // from F1 // template<class T> X(UniquePtr<T>); // from F2 // }; // direct-init of an X object with "new double(2.0)" as the initializer // selects the constructor that corresponds to the guide F1 with T = double // For F1 with T=double, the return type is UniquePtr<double> // result: // UniquePtr<double> dp{new auto(2.0)}
-
Or, for a more complex example (note: "S::N" would not compile: scope resolution qualifiers are not something that can be deduced):
template<class T> struct S { template<class U> struct N { N(T); N(T, U); template<class V> N(V, U); }; }; S<int>::N x{2.0, 1}; // the implicitly-generated deduction guides are (note that T is already known to be int) // F1: template<class U> S<int>::N<U> F(int); // F2: template<class U> S<int>::N<U> F(int, U); // F3: template<class U, class V> S<int>::N<U> F(V, U); // F4: template<class U> S<int>::N<U> F(S<int>::N<U>); (copy deduction candidate) // Overload resolution for direct-list-init with "{2.0, 1}" as the initializer // chooses F3 with U=int and V=double. // The return type is S<int>::N<int> // result: // S<int>::N<int> x{2.0, 1}; [TODO:? shouldn't be N<double>]
-
Aggregate deduction candidate (C++20)
- An
aggregate deduction candidate
may be added, if- C is defined and satisfies the requirements of an
aggregate type
with the assumption that any dependent base class has no virtual functions or virtual base classes, - there are no user-defined deduction guides for C, and
- the variable is initialized from a non-empty list of initializers arg1, arg2, ..., argn (which may use designated initializer)
- C is defined and satisfies the requirements of an
- The parameter list of the aggregate deduction candidate is produced from the aggregate element types, as follows:
- Let ei be the (possibly recursive) aggregate element (public base/class member/array element) that would be initialized from argi, where
- brace elision is only considered for members with non-dependent type or with array type of non-dependent bound,
- if C (or its element that is itself an aggregate) has a base that is a pack expansion:
- if the pack expansion is a trailing aggregate element, it is considered to match all remaining elements of the initializer list;
- otherwise, the pack is considered to be empty.
- If there's no such ei, the aggregate deduction candidate is not added.
- Otherwise, determine the parameter list T1, T2, ..., Tn of the aggregate deduction candidate as follows:
- If ei is an array and argi is either a braced-init-list or a string literal, Ti is rvalue reference to the type of ei.
- Otherwise, Ti is the type of ei.
- If a pack was skipped because it is a non-trailing aggregate element, an additional parameter pack of the form Pj ... is inserted in its original aggregate element position. (This will generally cause deduction to fail.)
- If a pack is a trailing aggregate element, the trailing sequence of parameters corresponding to it is replaced by a single parameter of the form Tn ....
- Let ei be the (possibly recursive) aggregate element (public base/class member/array element) that would be initialized from argi, where
- The aggregate deduction candidate is a fictional function template derived as above from a hypothetical constructor C(T1, T2, ..., Tn).
- During template argument deduction for the aggregate deduction candidate, the number of elements in a trailing parameter pack is only deduced from the number of remaining function arguments if it is not otherwise deduced.
template<class T> struct A { T t; struct { long a, b; } u; }; A a{1, 2, 3}; // aggregate deduction candidate: template<class T> A<T> F(T, long, long); template<class... Args> struct B : std::tuple<Args...>, Args... {}; B b{ std::tuple<std::any, std::string>{}, std::any{} }; // aggregate deduction candidate: // template<class... Args> B<Args...> F(std::tuple<Args...>, Args...); // type of b is deduced as B<std::any, std::string>
- An
- The syntax of a user-defined deduction guide is the syntax of a
function declaration
with a trailing return type, except that it uses the name of a class template as the function name:explicit-specifier(optional) template-name (parameter-declaration-clause) -> simple-template-id;
- User-defined deduction guides
must name a class template
and must be introduced within thesame semantic scope
of the class template (which could be namespace or enclosing class) and, for a member class template, must have thesame access
, but deduction guides do not become members of that scope. - A deduction guide is
not a function
and doesnot have a body
. Deduction guides arenot found by name lookup
and donot participate in overload resolution
except for the overload resolution against other deduction guides when deducing class template arguments. Deduction guidescannot be redeclared
in the same translation unit for the same class template.// declaration of the template template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; // additional deduction guide template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; // uses container c(7); // OK: deduces T=int using an implicitly-generated guide std::vector<double> v = { /* ... */}; auto d = container(v.begin(), v.end()); // OK: deduces T=double container e{5, 6}; // Error: there is no std::iterator_traits<int>::value_type
- The fictional constructors for the purpose of overload resolution (described above) are
explicit
if they correspond to an implicitly-generated deduction guide formed from an explicit constructor or to a user-defined deduction guide that is declared explicit. As always, such constructors are ignored in copy-initialization context:template<class T> struct A { explicit A(const T&, ...) noexcept; // #1 A(T&&, ...); // #2 }; int i; A a1 = { i, i }; // error: cannot deduce from rvalue reference in #2, // and #1 is explicit, and not considered in copy-initialization. A a2{i, i}; // OK, #1 deduces to A<int> and also initializes A a3{0, i}; // OK, #2 deduces to A<int> and also initializes A a4 = {0, i}; // OK, #2 deduces to A<int> and also initializes template<class T> A(const T&, const T&) -> A<T&>; // #3 template<class T> explicit A(T&&, T&&) -> A<T>; // #4 A a5 = {0, 1}; // error: #3 deduces to A<int&> // and #1 & #2 result in same parameter constructors. A a6{0,1}; // OK, #4 deduces to A<int> and #2 initializes A a7 = {0, i}; // error: #3 deduces to A<int&> A a8{0,i}; // error: #3 deduces to A<int&>
- Using a member
typedef
or alias template in a constructor or constructor template's parameter list does not, by itself, render the corresponding parameter of the implicitly generated guide a non-deduced context.template<class T> struct B { template<class U> using TA = T; template<class U> B(U, TA<U>); //#1 }; // Implicit deduction guide generated from #1 is the equivalent of // template<class T, class U> B(U, T) -> B<T>; // rather than // template<class T, class U> B(U, typename B<T>::template TA<U>) -> B<T>; // which would not have been deducible B b{(int*)0, (char*)0}; // OK, deduces B<char*>
-
Class template argument deduction is only performed if
no template argument list
is present. If a template argument list is specified, deduction does not take place.std::tuple t1(1, 2, 3); // OK: deduction std::tuple<int,int,int> t2(1, 2, 3); // OK: all arguments are provided std::tuple<> t3(1, 2, 3); // Error: no matching constructor in tuple<>. No deduction performed. std::tuple<int> t4(1, 2, 3); // Error
-
Class template argument deduction of
aggregates
typically requiresuser-defined deduction guides
: (C++20)template<class A, class B> struct Agg { A a; B b; }; // implicitly-generated guides are formed from default, copy, and move constructors template<class A, class B> Agg(A a, B b) -> Agg<A, B>; // ^ This deduction guide can be implicitly generated in C++20 Agg agg{1, 2.0}; // deduced to Agg<int, double> from the user-defined guide template <class... T> array(T&&... t) -> array<std::common_type_t<T...>, sizeof...(T)>; auto a = array{1, 2, 5u}; // deduced to array<unsigned, 3> from the user-defined guide
-
User-defined deduction guides do not have to be templates:
template<class T> struct S { S(T); }; S(char const*) -> S<std::string>; S s{"hello"}; // deduced to S<std::string>
-
Within the scope of a class template, the name of the template without a parameter list is an
injected class name
, and can be used as a type. In that case, class argument deduction does not happen and templateparameters must be supplied
explicitly:template<class T> struct X { X(T) { } template<class Iter> X(Iter b, Iter e) { } template<class Iter> auto foo(Iter b, Iter e) { return X(b, e); // no deduction: X is the current X<T> } template<class Iter> auto bar(Iter b, Iter e) { return X<typename Iter::value_type>(b, e); // must specify what we want } auto baz() { return ::X(0); // not the injected-class-name; deduced to be X<int> } };
-
In overload resolution,
partial ordering
takesprecedence
over whether a function template is generated from auser-defined deduction guide
: if the function template generated from the constructor is more specialized than the one generated from the user-defined deduction guide, the one generated from the constructor is chosen. Because thecopy deduction candidate
is typically more specialized than a wrapping constructor, this rule means thatcopying
is generallypreferred
overwrapping
.template<class T> struct A { A(T, int*); // #1 A(A<T>&, int*); // #2 enum { value }; }; template<class T, int N = T::value> A(T&&, int*) -> A<T>; //#3 A a{1,0}; // uses #1 to deduce A<int> and initializes with #1 A b{a,0}; // uses #2 (more specialized than #3) to deduce A<int> and initializes with #2
-
[Overload Resolution] When earlier tiebreakers, including partial ordering, failed to distinguish between two candidate function templates, the following rules apply:
User-defined > implicit
: A function template generated from a user-defined deduction guide is preferred over one implicitly generated from a constructor or constructor template.Copy deduction > implicit
: The copy deduction candidate is preferred over all other function templates implicitly generated from a constructor or constructor template.non-template > implicit
: A function template implicitly generated from a non-template constructor is preferred over a function template implicitly generated from a constructor template.
template <class T> struct A { using value_type = T; A(value_type); // #1 A(const A&); // #2 A(T, T, int); // #3 template<class U> A(int, T, U); // #4 }; // A(A); #5, the copy deduction candidate A x (1, 2, 3); // uses #3, generated from a non-template constructor template <class T> A(T) -> A<T>; // #6, less specialized than #5 A a (42); // uses #6 to deduce A<int> and #1 to initialize A b = a; // uses #5 to deduce A<int> and #2 to initialize template <class T> A(A<T>) -> A<A<T>>; // #7, as specialized as #5 A b2 = a; // uses #7 to deduce A<A<int>> and #1 to initialize
-
An rvalue reference to a cv-unqualified template parameter is
not a forwarding reference
if that parameter is aclass template parameter
:template<class T> struct A { template<class U> A(T&&, U&&, int*); // #1: T&& is not a forwarding reference, T&& can't collapse // U&& is a forwarding reference A(T&&, int*); // #2: T&& is not a forwarding reference }; template<class T> A(T&&, int*) -> A<T>; // #3: T&& is a forwarding reference, T&& can collapse int i, *ip; A a{i, 0, ip}; // error, cannot deduce from #1 A a0{0, 0, ip}; // uses #1 to deduce A<int> and #1 to initialize A a2{i, ip}; // uses #3 to deduce A<int&> and #2 to initialize
-
When initializing from a
single argument
of a type that is a specialization of the class template at issue,copying deduction
is generally preferred over wrapping by default:std::tuple t1{1}; //std::tuple<int> std::tuple t2{t1}; //std::tuple<int>, not std::tuple<std::tuple<int>> std::vector v1{1, 2}; // std::vector<int> std::vector v2{v1}; // std::vector<int>, not std::vector<std::vector<int>> (P0702R1) std::vector v3{v1, v2}; // std::vector<std::vector<int>>
-
Outside the special case for copying vs. wrapping, the strong preference for
initializer-list constructors
inlist-initialization
remains intact.std::vector v1{1, 2}; // std::vector<int> std::vector v2(v1.begin(), v1.end()); // std::vector<int> std::vector v3{v1.begin(), v1.end()}; // std::vector<std::vector<int>::iterator>
-
Before class template argument deduction was introduced, a common approach to avoiding explicitly specifying arguments is to use a
function template
:std::tuple p1{1, 1.0}; //std::tuple<int, double>, using deduction auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, pre-C++17
- Template declarations (class, function, and variables (C++14)) can appear inside a member specification of any class, struct, or union that aren't local classes.
Partial specializations
of member template may appear both atclass scope
and atenclosing namespace scope
, butexplicit (full) specializations
may only appear in any scope in which the primary template may appear.- If the enclosing class declaration is, in turn, a class template, when a member template is defined outside of the class body, it takes two sets of template parameters: one for the enclosing class, and another one for itself.
-
Destructors
andcopy constructors
cannot be templates. If a template constructor is declared which could be instantiated with the type signature of a copy constructor, the implicitly-declared copy constructor is used instead.-
635. Names of constructors and destructors of templates
template <class> struct S { S(); ~S(); }; template <class T> S<T>::S<T>() { } // error template <class T> S<T>::~S<T>() { } // okay
The reason for this is that 3.4.3.1 [class.qual] paragraph 2 says that S::S is "considered to name the constructor," which is not a template and thus cannot accept a template argument list. On the other hand, the second S in S::~S finds the injected-class-name, which "can be used with or without a template-argument-list" (14.6.1 [temp.local] paragraph 1) and thus satisfies the requirement to name the destructor's class (12.4 [class.dtor] paragraph 1).
-
-
A member function template
cannot be virtual
, and a member function template in a derived classcannot override
a virtual member function from the base class. -
A
non-template
member function and atemplate
member function with thesame name
may be declared. In case of conflict (when some template specialization matches the non-template function signature exactly), the use of that name and typerefers
to thenon-template member
unless an explicit template argument list is supplied.template<typename T> struct A { void f(int); // non-template member template<typename T2> void f(T2); // member template }; int main() { A<char> ac; ac.f('c'); // calls template function A<char>::f<char>(int) ac.f(1); // calls non-template function A<char>::f(int) ac.f<>(1); // calls template function A<char>::f<int>(int) }
-
An
out-of-class definition
of a member function templatemust be equivalent
to the declaration inside the class (see function template overloading for the definition of equivalency), otherwise it is considered to be anoverload
.struct X { template<class T> T good(T n); template<class T> T bad(T n); }; template<class T> struct identity { using type = T; }; // OK: equivalent declaration template<class V> V X::good(V n) { return n; } // Error: not equivalent to any of the declarations inside X template<class T> T X::bad(typename identity<T>::type n) { return n; }
- A user-defined conversion function can be a template.
- A user-defined conversion function template cannot have a deduced return type. (C++14)
- During overload resolution, specializations of conversion function templates are not found by name lookup. Instead, all visible conversion function templates are considered, and every specialization produced by template argument deduction (which has special rules for conversion function templates) is used as if found by name lookup.
- Using-declarations in derived classes cannot refer to specializations of template conversion functions from base classes.
struct A {
template<typename T>
operator T*(); // conversion to pointer to any type
};
// out-of-class definition
template<typename T>
A::operator T*() {return nullptr;}
// explicit specialization for char*
template<>
A::operator char*() {return nullptr;}
// explicit instantiation
template A::operator void*();
int main() {
A a;
int* ip = a.operator int*(); // explicit call to A::operator int*()
}
- A variable template declaration may appear at class scope, in which case it declares a static data member template. (C++14)
- Syntax
template <> declaration
- Any of the following can be fully specialized:
- function template
- class template
- (C++14)variable template
- member function of a class template
- member function template of a class or class template
- static data member of a class template
- member class of a class template
- member class template of a class or class template
- member enumeration of a class template
-
Explicit specialization may be declared in any scope where its primary template may be defined (which may be different from the scope where the primary template is defined; such as with out-of-class specialization of a member template) . Explicit specialization has to appear after the non-specialized template declaration.
-
Specialization
must be declared
before
thefirst
use that would causeimplicit instantiation
, in every translation unit where such use occursclass String {}; template<class T> class Array { }; template<class T> void sort(Array<T>& v) { } // primary template void f(Array<String>& v) { sort(v); // implicitly instantiates sort(Array<String>&), } // using the primary template for sort() template<> // ERROR: explicit specialization of sort(Array<String>) void sort<String>(Array<String>& v); // after implicit instantiation
-
A template specialization that
was declared
butnot defined
can be used just like any otherincomplete type
(e.g. pointers and references to it may be used)template<class T> class X; // primary template template<> class X<int>; // specialization (declared, not defined) X<int>* p; // OK: pointer to incomplete type X<int> x; // error: object of incomplete type
- When specializing a function template, its
template arguments can be omitted
if template argument deduction can provide them from the function argumentstemplate<class T> class Array { }; template<class T> void sort(Array<T>& v); // primary template template<> void sort(Array<int>&); // specialization for T = int // no need to write // template<> void sort<int>(Array<int>&);
- A function with the same name and the same argument list as a specialization is not a specialization (see template overloading in function template)
- An explicit specialization of a function template is
inline
only if it is declared with the inline specifier (or defined as deleted), it doesn't matter if the primary template is inline. Default function arguments
cannot be
specified in explicit specializations offunction templates
,member function templates
, andmember functions
of class templates when the class is implicitly instantiated.- An explicit specialization cannot be a
friend declaration
. TODO:? - If the primary template has a
exception
specification that isn't noexcept(false), the explicit specializations must have a compatible exception specification.
-
When
defining a member
of an explicitly specialized class templateoutside the body of the class
, the syntaxtemplate <>
is not used, except if it's a member of an explicitly specializedmember class template
, which is specialized as a class template, because otherwise, the syntax would require such definition to begin with template required by the nested templatetemplate< typename T> struct A { struct B {}; // member class template<class U> struct C { }; // member class template }; // 1. specialization of a member function template<> struct A<int> { void af(int); // member function of a specialization }; void A<int>::af(int) { } // template<> not used for a member of a specialization // 2. specialization of a member class template<> struct A<char>::B { void bf(); }; void A<char>::B::bf() { } // template<> not used for a member of a specialized member class either // 3. specialization of a member class template template<> template<class U> struct A<char>::C { void cf(); }; // template<> is used when defining a member of an explicitly // specialized member class template specialized as a class template template<> template<class U> void A<char>::C<U>::cf() { } template<> template<> void A<char>::C<char>::cf() { }
-
An explicit specialization of a
static data member
of a template is a definition if the declaration includes aninitializer
; otherwise, it is a declaration. These definitions must use braces for default initialization:template<> X Q<int>::x; // declaration of a static member template<> X Q<int>::x (); // error: function declaration template<> X Q<int>::x {}; // definition of a default-initialized static member
-
A member or a member template of a class template may be explicitly specialized for a given implicit instantiation of the class template, even if the member or member template is defined in the class template definition.
template<typename T> struct A { void f(T); // member, declared in the primary template void h(T) {} // member, defined in the primary template template<class X1> void g1(T, X1); // member template template<class X2> void g2(T, X2); // member template }; // specialization of a member template<> void A<int>::f(int); // member specialization OK even if defined in-class template<> void A<int>::h(int) {} // out of class member template definition template<class T> template<class X1> void A<T>::g1(T, X1) { } // member template specialization template<> template<class X1> void A<int>::g1(int, X1); // member template specialization template<> template<> void A<int>::g2<char>(int, char); // for X2 = char // same, using template argument deduction (X1 = char) template<> template<> void A<int>::g1(int, char);
-
Member or a member template may be
nested
within many enclosing class templates. In an explicit specialization for such a member, there's a template<> for every enclosing class template that is explicitly specialized.template<class T1> struct A { template<class T2> struct B { template<class T3> void mf(); }; }; template<> struct A<int>; template<> template<> struct A<char>::B<double>; template<> template<> template<> void A<char>::B<char>::mf<double>();
-
In such a nested declaration, some of the levels may
remain unspecialized
(except thatit can't specialize a class member template if its enclosing class is unspecialized
). For each of those levels, the declaration needs template, because such specializations are themselves templates.template <class T1> class A { template<class T2> class B { template<class T3> void mf1(T3); // member template void mf2(); // non-template member }; }; // specialization template<> // for the specialized A template<class X> // for the unspecialized B class A<int>::B { template <class T> void mf1(T); }; // specialization template<> // for the specialized A template<> // for the specialized B template<class T> // for the unspecialized mf1 void A<int>::B<double>::mf1(T t) { } // ERROR: B<double> is specialized and is a member template, so its enclosing A // must be specialized also template<class Y> template<> void A<Y>::B<double>::mf2() { }
-
Syntax
template <parameter-list> class-key class-head-name <argument-list> declaration (1) template <parameter-list> decl-specifier-seq declarator <argument-list> initializer(optional) (2) (C++14)
-
Allows customizing
class
andvariable
(C++14) templates for a given category of template arguments. -
This declaration must be in the same namespace or, for member templates, class scope as the primary template definition which it specializes.
template<class T1, class T2, int I>
class A {}; // primary template
template<class T, int I>
class A<T, T*, I> {}; // #1: partial specialization where T2 is a pointer to T1
template<class T, class T2, int I>
class A<T*, T2, I> {}; // #2: partial specialization where T1 is a pointer
template<class T>
class A<int, T*, 5> {}; // #3: partial specialization where T1 is int, I is 5, and T2 is a pointer
template<class X, class T, int I>
class A<X, T*, I> {}; // #4: partial specialization where T2 is a pointer
- The following restrictions apply to the argument-list of a partial template specialization:
-
The specialization has to be more specialized than the primary template (C++11)
template<int N, typename T1, typename... Ts> struct B; template<typename... Ts> struct B<0, Ts...> { }; // Error: not more specialized
-
Default arguments
cannot appear in the argument list -
If any argument is a
pack expansion
, it must be the last argument in the list -
Non-type argument
expressions can use template parameters as long as the parameter appears at least once outside a non-deduced context (note that only clang supports this feature currently) -
Non-type template
argument cannot specialize a template parameter whose typedepends on
a parameter of the specializationtemplate <class T, T t> struct C {}; // primary template template <class T> struct C<T, 1>; // error: type of the argument 1 is T, which depends on the parameter T template<int X, int (*array_ptr)[X]> class B {}; // primary template int array[5]; template<int X> class B<X, &array> { }; // error: type of the argument &array is int(*)[X], which depends on the parameter X
-
- Partial template specializations are not found by name lookup. Only if the primary template is found by name lookup, its partial specializations are considered. In particular, a using declaration that makes a primary template visible, makes partial specializations visible as well.
-
When a class or variable (C++14) template is instantiated, and there are partial specializations available, the compiler has to decide if the primary template is going to be used or one of its partial specializations.
- If only
one specialization
matches the template arguments, that specialization is used - If
more than one specialization
matches, partial order rules are used to determine which specialization is more specialized. Themost specialized
specialization is used, if it is unique (if it is not unique, the program cannot be compiled) - If
no specializations
match, theprimary template
is used
- If only
-
Overload Resolution To establish
more-specialized-than relationship
between partial specializations, each is first converted to afictitious function template
as follows:- The first function template has the same template parameters as the first partial specialization and has just one function parameter, whose type is a class template specialization with all the template arguments from the first partial specialization
- the second function template has the same template parameters as the second partial specialization and has just one function parameter whose type is a class template specialization with all the template arguments from the second partial specialization.
- The function templates are then ranked as if for
function template overloading
.
template<int I, int J, class T> struct X { }; // primary template template<int I, int J> struct X<I, J, int> { // partial specialization #1 static const int s = 1; // fictitious function template is template<int I, int J> void f(X<I, J, int>); #A }; template<int I> struct X<I, I, int> { // partial specialization #2 static const int s = 2; // fictitious function template is template<int I> void f(X<I, I, int>); #B }; int main() { X<2, 2, int> x; // both #1 and #2 match // partial ordering for function templates: // #A from #B: void(X<I,J,int>) from void(X<U1, U1, int>): deduction ok // #B from #A: void(X<I,I,int>) from void(X<U1, U2, int>): deduction fails // #B is more specialized // #2 is the specialization that is instantiated std::cout << x.s << '\n'; // prints 2 }
- The
template parameter list
and thetemplate argument list
of a member of a partial specialization mustmatch
the parameter list and the argument list of thepartial specialization
. - Just like with members of primary templates, they only need to be
defined if used
in the program. - Members of partial specializations are not related to the members of the primary template.
- Explicit (full) specialization of a member of a partial specialization is declared the same way as an explicit specialization of the primary template.
template<class T, int I> // primary template struct A { void f(); // member declaration }; template<class T, int I> void A<T,I>::f() { } // primary template member definition // partial specialization template<class T> struct A<T,2> { void f(); void g(); // Rule 3 void h(); // Rule 3 }; // member of partial specialization template<class T> void A<T,2>::g() { } // explicit (full) specialization of a member of partial specialization template<> void A<char,2>::h() {} int main() { A<char,0> a0; A<char,2> a2; a0.f(); // OK, uses primary template’s member definition a2.g(); // OK, uses partial specialization's member definition a2.h(); // OK, uses fully-specialized definition of the member of a partial specialization a2.f(); // error: no definition of f() in the partial // specialization A<T,2> (the primary template is not used) }
- If a primary template is a member of another class template, its partial specializations are members of the enclosing class template.
- If the enclosing template is instantiated, the declaration of each member partial specialization is instantiated as well (the same way declarations, but not definitions, of all other members of a template are instantiated).
- If the primary member template is explicitly (fully) specialized for a given (implicit) specialization of the enclosing class template, the partial specializations of the member template are ignored for this specialization of the enclosing class template.
- If a partial specialization of the member template is explicitly specialized for a given (implicit) specialization of the enclosing class template, the primary member template and its other partial specializations are still considered for this specialization of the enclosing class template.
template<class T> struct A { // enclosing class template template<class T2> struct B {}; // primary member template template<class T2> struct B<T2*> {}; // partial specialization of member template }; template<> template<class T2> struct A<short>::B {}; // full specialization of primary member template // (will ignore the partial) A<char>::B<int> abci; // uses primary A<char>::B<int*> abcip; // uses partial specialization T2=int A<short>::B<int*> absip; // uses full specialization of the primary (ignores partial)
- Syntax
type ... Args(optional) // (1) non-type template parameter pack typename|class ... Args(optional) // (2) type template parameter pack template <parameter-list> typename(C++17)|class ... Args(optional) // (3) template template parameter pack Args ... args(optional) // (4) function parameter pack pattern ... // (5) Parameter pack expansion
- In a
primary class template
, the template parameter pack must be thefinal parameter
in the template parameter list. In afunction template
, the template parameter pack mayappear earlier
in the list provided that all following parameters can be deduced from the function arguments, or have default arguments:template<typename... Ts, typename U> struct Invalid; // Error: Ts.. not at the end template<typename ...Ts, typename U, typename=void> void valid(U, Ts...); // OK: can deduce U // void valid(Ts..., U); // Can't be used: Ts... is a non-deduced context in this position valid(1.0, 1, 2, 3); // OK: deduces U as double, Ts as {int,int,int}
- A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order.
- If the names of
two parameter packs
appear in the same pattern, they are expanded simultaneously, and they must have the same length: - If a pack expansion is nested within another pack expansion, the parameter packs that appear inside the innermost pack expansion are expanded by it, and there must be another pack mentioned in the enclosing pack expansion, but not in the innermost one:
template<class ...Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) is the pattern, it expands two packs // (Args and args) simultaneously f(h(args...) + args...); // Nested pack expansion: // inner pack expansion is "args...", it is expanded first // outer pack expansion is h(E1, E2, E3) + args..., it is expanded // second (as h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3) }
- Function argument lists
- Function parameter list
- Template parameter list
- Template argument lists
- Parenthesized initializers
- Brace-enclosed initializers
- Lambda captures
- The sizeof... operator
- Dynamic exception specifications
- Base specifiers and member initializer lists
- Syntax
( pack op ... ) // (1) unary right fold ( ... op pack ) // (2) unary left fold ( pack op ... op init ) // (3) binary right fold ( init op ... op pack ) // (4) binary left fold
- "Substitution Failure Is Not An Error"
A tuple can be thought of as a "quick and dirty" data struct.
Tuple type:
tuple<T1, T2, T3...> t; tuple<T1, T2, T3...> t(V1, V2, V3..); make_tuple(V1, V2, V3...); get<i>(t);
tuple_size<tupleType>::value; tuple_element<i, tupleType>::type; swap(...);
Bitset:
bitset<n> b; bitset<n> b(u); biset<n> b(s, pos, m, zero, one); bitset<n> b(cp, pos, m, zero, one);
any() all() none() count() test(pos) set(pos, v) set() reset(pos) flip(pos) flip()
Regular Expression:
Regex regex_match regex_search regex_replace regex_iterator smatch ssub_match
(seq, m, r, mfg) (seq, r, mft)
- Each namespace is scope
- Namespace can be discontiguous
- Namespaces that defien multiple, unrelated types, should use separate files to represent each types that the namespace defines.
- Namespace members are possible to defined outside the namespace, but not in the unrelated namespace.
- inline namespace: can be used as if they were direct members of the enclosing namespace.
- unnamed namespace, variables defiend in an unnamed space have static lifetime: they are created before their first use and destroyed when the program ends.
- The use of file static declarations is deprecated by the C++ standard. File static should be avoided and unnamespaces used instead.
Namespace aliases
namespace cplusplus_primer{};
namespace primer = cplusplus_primer;
- using Declaration
- introduce namespace members into other namespaces and block scopes
- introduce base class members into derived class definitions
- Introduces a
member of a base class
into the derived class definition, such as to expose a protected member of base as public member of derived. - If the name is the name of an
overloaded member function
of the base class, all base class member functions with that name are introduced - If the derived class already has a member with the same name, parameter list, and qualifications, the derived class member
hides or overrides
(doesn't conflict with) the member that is introduced from the base class.
- Introduces a
- Inheriting constructors
- If the using-declaration refers to a constructor of a direct base of the class being defined (e.g. using Base::Base;),
all constructors
of that base (ignoring member access) are made visible to overload resolution when initializing the derived class. - If overload resolution selects one of the inherited constructors when initializing an object of such derived class:
- Then the Base subobject from which the constructor was inherited is initialized using the inherited constructor
- And all other bases and members of Derived are initialized as if by the defaulted default constructor (default member initializers are used if provided, otherwise default initialization takes place).
- The entire initialization is treated as a single function call: initialization of the parameters of the inherited constructor is sequenced-before initialization of any base or member of the derived object.
- If the constructor was inherited from
multiple base class
subobjects of type B, the program is ill-formed, similar to multiply-inherited non-static member functions - If an inherited constructor matches the signature of one of the constructors of Derived, it is
hidden
from lookup by the version found in Derived - If one of the inherited constructors of Base happens to have the signature that matches a
copy/move
constructor of the Derived, it does not prevent implicit generation of Derived copy/move constructor (which then hides the inherited version, similar to using operator=).
- If the using-declaration refers to a constructor of a direct base of the class being defined (e.g. using Base::Base;),
- introduce enumerators into namespaces, block, and class scopes (C++20)
- A using-declaration can also be used with unscoped enumerators.
- using Directives
- Allows us to use the unqualied form of a namespace name. Unlike a using declaration, we retain no control over which names are made visile---- they all are.
using namespace std::string;
- Using directives and scope
Arguemnt-Dependent Lookup and Parameter of Class Type
- vptr and vtbl(virutal function table and it's pointer):
[refer]https://www.nowcoder.com/profile/3669004/test/13503903/22584#summary
http://blog.csdn.net/haoel/article/details/1948051
- When new an object, the compiler only allocates space for member variables while member functions are shared among objects belonging to same class.
- The compiler adds _vptr to class automatically, and ensures contents of _vtbl at compile time, initializes _vptr in constructor of the class. _vptr at the begining of the class in memory. The objects of the class share the _vtbl of the class.
- _vtbl is essentially an array of pointers that hold function pointers
- In multi-inheritance, each base classes _vtbl stored in the derived class in defined order, and derived class shares the same _vptr as the first base class.
- virtual functions declared in the derived class not only overwrite the virtual functions corresponding to each base class but also add to virtual function table of the first base class.
- _vptr and _vtbl memory Layout: _vptr stored in the begining of the class momory in heap. _vtbl stored in .rodata segment. virtual functions themselves stored in .text segment.
- Polymorphism process: base pointer(stack) ==> _vptr(heap) ==> _vtbl(.rodata) ==> .text
- Scope(::) operator purpose:
- To access the global variable when there is a local variale with the same name.
- To define functions outside the class.
- To access class's staic variable.
- In case of the multiple Inheritance.
- Single, multiple and virtual inheritance:
Synchronization with Atomics in C++20
- Atomics guarantee two characteristics:🔗 Ref
- they are atomic
- they provide synchronisation and order constraints on the program execution.
- There are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed
- A
load operation
with this memory order performs a consume operation on the affected memory location:- no reads or writes in the current thread dependent on the value currently loaded can be reordered
before
this load.
- no reads or writes in the current thread dependent on the value currently loaded can be reordered
- Writes to data-dependent variables in other threads that release the same atomic variable are visible in the current thread. On most platforms, this affects compiler optimizations only.
- The std::memory_order_consume is about data dependencies on atomics. Both dependencies introduce a happens-before relation. 🔗 Ref
carries-a-dependency-to
: If the result of an operation A is used as an operand of an operation B, then: A carries-a-dependency-to B.dependecy-ordered-before
: A store operation (with std::memory_order_release, std::memory_order_acq_rel or std::memory_order_seq_cst), is dependency-ordered-before a load operation B (with std::memory_order_consume), if the result of the load operation B is used in a further operation C in the same thread. The operations B and C have to be in the same thread.
- A
load operation
with this memory order performs the acquire operation on the affected memory location:- no reads or writes in the current thread can be reordered
before
this load.
- no reads or writes in the current thread can be reordered
- All writes in other threads that release the same atomic variable are visible in the current thread
- Acquire semantics prevent memory reordering of the read-acquire with any read or write operation that follows it in program order.
- A
store operation
with this memory order performs the release operation:- no reads or writes in the current thread can be reordered
after
this store.
- no reads or writes in the current thread can be reordered
- All writes in the current thread are visible in other threads that acquire the same atomic variable and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic
- Release semantics prevent memory reordering of the write-release with any read or write operation that precedes it in program order.
- A
read-modify-write
operation with this memory order is both an acquire operation and a release operation. - No memory reads or writes in the current thread can be reordered before or after this store.
- All writes in other threads that release the same atomic variable are visible before the modification and the modification is visible in other threads that acquire the same atomic variable.
- The acquire-release semantic is the key for a deeper understanding of the multithreading programming because the threads will be synchronised at specific synchronisation points in the code. Without these synchronisation points, there is no well-defined behaviour of threads, tasks or condition variables possible. 🔗 Ref
- The acquire-release semantic is based on one key idea: 🔗 Ref
- A release operation synchronises with an acquire operation on the same atomic and establishes an ordering constraint.
- So, all read and write operations can not be moved before an acquire operation, all read and write operations can not be move behind a release operation.
- A
load operation
with this memory order performs an acquire operation, astore performs
a release operation, andread-modify-write
performs both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order - Sequential consistency provides two guarantees. 🔗 Ref
- The instructions of a program are executed in source code order.
- There is a global order of all operations on all threads.
- Weak vs. Strong Memory Models
- This Is Why They Call It a Weakly-Ordered CPU
- Memory Barriers Are Like Source Control Operations
- Acquire and Release Semantics
- Memory Ordering at Compile Time
- The Happens-Before Relation
- The Synchronizes-With Relation
- An Introduction to Lock-Free Programming
- Acquire and Release Fences
- Acquire and Release Fences Don't Work the Way You'd Expect
- Roll Your Own Lightweight Mutex
- Can Reordering of Release/Acquire Operations Introduce Deadlock?
- You Can Do Any Kind of Atomic Read-Modify-Write Operation
- Atomic vs. Non-Atomic Operations
- Memory Reordering Caught in the Act
- Implementing a Recursive Mutex
- Two very interesting properties:
- the only lock-free atomic.
- the building block for higher thread abstractions.
Synchronization with Atomics in C++20
-
Lost wakeup
:- The sender sends its notification before the receiver gets to its wait state. The consequence is that the notification is lost.
- The C++ standard describes condition variables as a simultaneous synchronisation mechanism: "The condition_variable class is a synchronisation primitive that can be used to block a thread, or multiple threads at the same time, ...". So the notification gets lost, and the receiver is waiting and waiting and ... .
-
Spurious wakeup
:- Usually happen because, in between the time when the condition variable was signaled and when the waiting thread finally ran, another thread ran and changed the condition. There was a race condition between the threads, with the typical result that sometimes, the thread waking up on the condition variable runs first, winning the race, and sometimes it runs second, losing the race. At a minimum POSIX Threads and the Windows API can be victims of these phenomena.
- Some implementations (in particular many implementations of pthreads) recognize this situation and avoid this "hurry up and wait" scenario by transferring the waiting thread from the condition variable's queue directly to the queue of the mutex within the notify call, without waking it up
-
The predicate protects against both flaws:
- Lost wakeup: the thread checks the predicate when it is going to sleep. If it's ture, will not go to sleep.
- Spurious wakeup: if the predicate is false, continue waiting.
The notification would be lost when the sender sends its notification before the receiver is in the wait state and does not use a predicate. Consequently, the receiver waits for something that never happens. This is a deadlock.
C++ Core Guidelines: Be Aware of the Traps of Condition Variables
When you only need a one-time notification such as in the previous program, promises and futures are a better choice than condition variables. Promise and futures cannot be victims of spurious or lost wakeups.
Synchronization with Atomics in C++20
Thread Synchronization with Condition Variables or Tasks
Criteria | Condition Variables | Task (Promise - Future) |
---|---|---|
Multiple synchronization Possible | Y | N |
Critical region | Y | N |
Exception handling in receiver | N | Y |
Spurious wakeup | Y | N |
Lost wakeup | Y | N |
The benefit of a condition variable to a promise and future is, that you can use condition variables to synchronize threads multiple times. In opposite to that, a promise can send its notification only once.
There is only one downside to using promises and futures: they can only be used once. If you want to communicate more than once, you have to use condition variables or atomics.
-
The main usage of the CRTP is to implementing:
- static polymorphism
- static interface
- adding functionality to original class
-
In general, a class deriving from another class expresses that the derived class somehow conceptually "is a" base class. The purpose is to use the base class in generic code, and to redirect calls to the base class over to code in the derived class.
-
With the CRTP the situation is radically different. The derived class does not express the fact it "is a" base class. Rather, it expands its interface by inherting from the base class, in order to add more functionality.
-
Therefore the base class is not the interface, and the derived class is not the implementation
-
Drawbacks:
- Derivec class may hide the base class function if they have the same function name (can solve by mixin class)
- There is a level of indirection that is inherent to the CRTP: have to implement functionality in the base and derived classes (cal solve by mixin class)
- It doesn’t clearly express the intention that it is constraining the API of CRTP (can solve by concepts) */
namespace crtp {
template <typename T>
struct crtp
{
T& derive() { return static_cast<T&>(*this); }
const T& derive() const { return static_cast<const T&>(*this); }
private:
/* private constructor prevents the case: class Derived2 : public Base<Derived1> */
crtp() = default;
friend T;
};
/* phantom type is used to solve diamond inheritance problem */
template <typename T, template<typename> class crtpType>
struct crtp
{
T& derive() { return static_cast<T&>(*this); }
const T& derive() const { return static_cast<const T&>(*this); }
private:
crtp() = default;
friend crtpType<T>;
};
template<typename Printable>
struct RepeatPrint
{
void repeat(unsigned int n) const {
while (n-- > 0) {
static_cast<Printable const&>(*this).print();
}
}
};
class Name : public RepeatPrint<Name>
{
public:
Name(std::string firstName, std::string lastName)
: firstName_(std::move(firstName)),
lastName_(std::move(lastName))
{}
void print() const {
std::cout << lastName_ << ", " << firstName_ << '\n';
}
private:
std::string firstName_;
std::string lastName_;
};
int main() {
Name ned("Eddard", "Stark");
ned.repeat(10);
}
} // namspace crtp
-
Mixin classes are template classes that define a generic behaviour, and are designed to inherit from the type you wish to plug their functionality onto.
-
The idea of the mixin class is to isolate the generic functionality into its own class, template this class on the type we want to plug in onto, and derive from that type.
-
Mixin classes involve a mix of template and inheritance in order to plug a generic functionality onto an existing class.
-
Mixin classes are like the CRTP, but upside down
namespace mixin {
class Name {
public:
Name(std::string firstName, std::string lastName)
: firstName_(std::move(firstName)),
lastName_(std::move(lastName))
{}
void print() const {
std::cout << lastName_ << ", " << firstName_ << '\n';
}
private:
std::string firstName_;
std::string lastName_;
};
template<typename Printable>
struct RepeatPrint : Printable {
explicit RepeatPrint(Printable const& printable) : Printable(printable) {}
void repeat(unsigned int n) const {
while (n-- > 0) {
this->print();
}
}
};
int main() {
Name ned("Eddard", "Stark");
RepeatPrint<Name>(ned).repeat(10);
}
} // namespace mixin
-
CRTP and mixin classes provide two approaches to the same problem:
- adding a generic functionality to an existing class, but with different trade-offs.
-
Here are the points where they differ:
-
The CRTP:
- impacts the definition of the original class, because it has to inherit from the CRTP,
- client code uses the original class directly and benefits from its augmented functionalities.
-
The mixin class:
- leaves the original class unchanged,
- client code doesn’t use the original class directly, it needs to wrap it into the mixin to use the augmented functionality,
- inherits from a the original class even if it doesn’t have a virtual destructor. This is ok unless the mixin class is deleted polymorphically through a pointer to the original class.
- Curiously Recurring Template Pattern (CRTP)
- The CRTP, episode One: Definition
- The CRTP, episode Two: What the CRTP can bring to your code
- The CRTP, episode Three: An implementation helper for the CRTP
- Mixin Classes: The Yang of the CRTP
- How to Turn a Hierarchy of Virtual Methods into a CRTP
- Replacing CRTP Static Polymorphism With Concepts
- Removing Duplicates in C++ CRTP Base Classes
- How to Reduce the Code Bloat of a Variadic CRTP
- Variadic CRTP Packs: From Opt-in Skills to Opt-in Skillsets
- Variadic CRTP: An Opt-in for Class Features, at Compile Time