- Class template declaration 、 Implement and use
- Class Instantiation
- Use part of the member function of the class template
- Friends
- Full specialization of class template
- Partial specialization of class templates
- Default template parameters
- Type Aliases
- Parameter derivation of class template Class Template Argument Deduction
Class template declaration 、 Implement and use
Statement :
template <typename T>
class Stack {
private:
std::vector<T> elems; // elements
public:
void push(T const &elem); // push element
void pop(); // pop element
T const &top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
Realization :
template <typename T>
void Stack<T>::push(T const &elem) {
elems.push_back(elem); // append copy of passed elem
}
template <typename T>
void Stack<T>::pop() {
assert(!elems.empty());
elems.pop_back(); // remove last element
}
template <typename T>
T const &Stack<T>::top() const {
assert(!elems.empty());
return elems.back(); // return copy of last element
}
Use :
int main() {
Stack<int> intStack; // stack of ints
Stack<std::string> stringStack; // stack of strings
// manipulate int stack
intStack.push(7);
std::cout << intStack.top() << '\n';
// manipulate string stack
stringStack.push("hello");
std::cout << stringStack.top() << '\n';
stringStack.pop();
}
There are two points to note
- The constructor inside the class declaration 、 copy constructor 、 Destructor 、 Assignment, etc. where class names are used , Can be
Stack<T>
Shorthand forStack
, for example :
template<typename T>
class Stack {
...
Stack (Stack const&); // copy constructor
Stack& operator= (Stack const&); // assignment operator
...
};
But outside the class , Still need Stack<T>
:
template<typename T>
bool operator== (Stack<T> const& lhs, Stack<T> const& rhs);
- Class templates cannot be declared or defined in function or block scope . Generally, class templates can only be defined in global/namespace Scope , Or in the declaration of other classes .
Class Instantiation
instantiation The concept of is said in the function template . In class templates , Class template functions are only called when they are called instantiate
. In the example above ,push()
and top()
Will be Stack<int>
and Stack<std::string>
the instantiate
, however pop()
Only be Stack<std::string>
the instantiate
.
Use part of the member function of the class template
We are Stack New offer printOn()
function , This needs to be elem
Support <<
operation :
template<typename T>
class Stack {
...
void printOn() (std::ostream& strm) const {
for (T const& elem : elems) {
strm << elem << ' '; // call << for each element
}
}
};
According to the previous section about class templates instantiation
, Only when the function is used will the function be executed instantiation
. If our template parameter is an element, it does not support <<
Of std::pair< int, int>
, You can still use other functions of the class template , Only a call printOn
It's only when you make a mistake :
Stack<std::pair< int, int>> ps; // note: std::pair<> has no operator<<
defined
ps.push({4, 5}); // OK
ps.push({6, 7}); // OK
std::cout << ps.top().first << ’\n’; // OK
std::cout << ps.top().second << ’\n’; // OK
ps.printOn(std::cout); // ERROR: operator<< not supported for element type
Concept
This raises a question , How do we know what operations a class template and its template functions need ?
stay c++11 in , We have static_assert
:
template<typename T>
class C
{
static_assert(std::is_default_constructible<T>::value, "Class C requires default-constructible elements");
...
};
If do not have static_assert, The template parameters provided do not satisfy std::is_default_constructible
, The code doesn't compile well . But the error messages generated by the compiler can be very long , Contains the entire template instantiation
Information about —— From the beginning instantiation
Until the wrong place , It's hard to find out the real cause of the mistake .
So use static_assert It's a way . however static_assert For making simple judgments , In the actual scenario, our scenario will be more complex , For example, to determine whether a template parameter has a specific member function , Or ask them to support comparison , Use... In this case concept It's more appropriate .
concept yes c++20 A feature used to indicate template library constraints in , It will be explained separately later concept, For the sake of the length of the article, I'll just talk about why there should be concept.
Friends
First of all, it needs to be clear : Although friend looks like a member of this class , But friends don't belong to this category . Here friend refers to friend function and friend class . This is essential for understanding the following grammatical rules .
Mode one
template<typename T>
class Stack {
...
void printOn(std::ostream &strm) const {
for (T const &elem : elems) {
strm << elem << ' '; // call << for each element
}
}
template <typename U>
friend std::ostream &operator<<(std::ostream &, Stack<U> const &);
};
template <typename T>
std::ostream &operator<<(std::ostream &strm, Stack<T> const &s) {
s.printOn(strm);
return strm;
}
int main() {
Stack<std::string> s;
s.push("hello");
s.push("world");
std::cout << s;
return 0;
}
Here, the friend function declared in the class uses template parameters different from the class template <template typename U>
, Because the template parameters of friend function and class template do not affect each other , This can be understood as creating a new function template .
Another example of a friend class :
template<typename T>
class foo {
template<typename U>
friend class bar;
};
Different template parameters are also used here . That is to say :bar<char>
、bar<int>
、bar<float>
And any other type of bar All are foo<char>
Friends .
Mode two
template<typename T>
class Stack;
template<typename T>
std::ostream& operator<< (std::ostream&, Stack<T> const&);
template<typename T>
class Stack {
...
friend std::ostream& operator<< <T> (std::ostream&, Stack<T> const&);
};
It's stated here in advance that Stack
operator<<
, And in the class template ,
operator<<
I used
<T>
, No new template parameters are used .
Compared with the first way , Here we create a special non member function template as a friend ( Notice the declaration of this friend function , It's not <T>
Of ).
The example of the second friend class in mode 1 is written in this way as follows :
template<typename T>
class bar;
template<typename T>
struct foo {
friend class bar<T>;
};
In contrast to , There is only bar<char>
yes foo<char>
Friends class .
There are many friend rules for class templates , It's OK to know what kind of rules there are (Friend Classes of Class Templates、Friend Functions of Class Templates、Friend Templates), When you use it, you can check it again . You can refer to :《C++ Templates Second Edition》12.5 Section . ( Official account : Hong Chen smiles . reply : e-book obtain pdf)
Full specialization of class template
Similar to function templates , But here's the thing , If you want to fully specialize a class template , You have to fully specialize all the member functions of this class template .
template <>
class Stack<std::string> {
private:
std::deque<std::string> elems; // elements
public:
void push(std::string const &); // push element
void pop(); // pop element
std::string const &top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
void Stack<std::string>::push(std::string const &elem) {
elems.push_back(elem); // append copy of passed elem
}
void Stack<std::string>::pop() {
assert(!elems.empty());
elems.pop_back(); // remove last element
}
std::string const &Stack<std::string>::top() const {
assert(!elems.empty());
return elems.back(); // return copy of last element
}
At the beginning of the class declaration , Need to use template<>
It also indicates the type of the fully specialized parameter of the class template :
template<>
class Stack<std::string> {
...
};
In member functions , Need to put T
Replace with a specialized parameter type :
void Stack<std::string>::push (std::string const& elem) {
elems.push_back(elem); // append copy of passed elem
}
Partial specialization of class templates
Class templates can be partially specialized for some feature scenarios , For example, we make partial specialization for template parameter is pointer :
// partial specialization of class Stack<> for pointers:
template <typename T>
class Stack<T *> {
private:
std::vector<T *> elems; // elements
public:
void push(T *); // push element
T *pop(); // pop element
T *top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
template <typename T>
void Stack<T *>::push(T *elem) {
elems.push_back(elem); // append copy of passed elem
}
template <typename T>
T *Stack<T *>::pop() {
assert(!elems.empty());
T *p = elems.back();
elems.pop_back(); // remove last element
return p; // and return it (unlike in the general case)
}
template <typename T>
T *Stack<T *>::top() const {
assert(!elems.empty());
return elems.back(); // return copy of last element
}
Notice the difference between class declaration and full specialization :
template<typename T>
class Stack<T*> {
};
Use :
Stack<int*> ptrStack; // stack of pointers (special implementation)
ptrStack.push(new int{42});
Partial specialization of multi template parameters
Similar to function template overloading , It's easy to understand .
The original template :
template<typename T1, typename T2>
class MyClass {
...
};
heavy load :
// partial specialization: both template parameters have same type
template<typename T>
class MyClass<T,T> {
...
};
// partial specialization: second type is int
template<typename T>
class MyClass<T,int> {
...
};
// partial specialization: both template parameters are pointer types
template<typename T1, typename T2>
class MyClass<T1*,T2*> {
...
};
Use :
MyClass<int,float> mif; // uses MyClass<T1,T2>
MyClass<float,float> mff; // uses MyClass<T,T>
MyClass<float,int> mfi; // uses MyClass<T,int>
MyClass<int*,float*> mp; // uses MyClass<T1*,T2*>
There will also be overload conflicts :
MyClass<int,int> m; // ERROR: matches MyClass<T,T> and MyClass<T,int>
MyClass<int*,int*> m; // ERROR: matches MyClass<T,T> and MyClass<T1*,T2*>
Default template parameters
It's also similar to the default parameter of a function . For example, we do it for Stack<>
Add a default parameter , On behalf of management Stack The container type of the element :
template <typename T, typename Cont = std::vector<T>>
class Stack {
private:
Cont elems; // elements
public:
void push(T const &elem); // push element
void pop(); // pop element
T const &top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
template <typename T, typename Cont>
void Stack<T, Cont>::push(T const &elem) {
elems.push_back(elem); // append copy of passed elem
}
template <typename T, typename Cont>
void Stack<T, Cont>::pop() {
assert(!elems.empty());
elems.pop_back(); // remove last element
}
template <typename T, typename Cont>
T const &Stack<T, Cont>::top() const {
assert(!elems.empty());
return elems.back(); // return copy of last element
}
Notice that the template parameter that defines the member function becomes 2 individual :
template<typename T, typename Cont>
void Stack<T,Cont>::push (T const& elem) {
elems.push_back(elem); // append copy of passed elem
}
Use :
// stack of ints:
Stack<int> intStack;
// stack of doubles using a std::deque<> to manage the elements
Stack<double,std::deque<double>> dblStack;
Type Aliases
new name for complete type
Two ways :typedef、using(c++11)
- typedef
typedef Stack<int> IntStack;
void foo (IntStack const& s);
IntStack istack[10];
- using
using IntStack = Stack<int>;
void foo (IntStack const& s);
IntStack istack[10];
alias template
using Than typedef One big advantage is that you can define alias template:
template <typename T>
using DequeStack = Stack<T, std::deque<T>>; // stack of strings
int main() {
DequeStack<int> ds;
return 0;
}
Just a little bit more , Class templates cannot be declared or defined in function or block scope . Generally, class templates can only be defined in global/namespace Scope , Or in the declaration of other classes .
In the previous function template article std::common_type_t
, It's actually an alias :
template <class ..._Tp> using common_type_t = typename common_type<_Tp...>::type;
Alias Templates for Member Types
- typedef:
struct C {
typedef ... iterator;
...
};
- using:
struct MyType {
using iterator = ...;
...
};
Use :
template<typename T>
using MyTypeIterator = typename MyType<T>::iterator; // typename There has to be
MyTypeIterator<int> pos;
keyword typename
The note above says :typename MyType<T>::iterator
Inside typename
Is a must , Because of the typename Represents a type defined within a class , otherwise ,iterator
It will be treated as a static variable or an enumeration :
template <typename T> class B {
public:
static int x; // Static variables within a class
using iterator = ...; // Types defined within a class
};
template <typename T>
int B<T>::x = 20;
int main() {
std::cout << B<int>::x; // 20
return 0;
}
Using or Typedef
Individuals tend to use using :
- using Use
=
, More in line with the habit of reading code 、 Clearer :
typedef void (*FP)(int, const std::string&); // typedef
using FP = void (*)(int, const std::string&); // using
- As mentioned above ,using Definition
alias template
More convenient .
Parameter derivation of class template Class Template Argument Deduction
You may feel that the type of template parameter that you need to display every time you use a template is superfluous , If a class template can look like auto
Just like automatically deriving template types . stay C++17 in , The idea became possible : If the constructor can deduce all the template parameters , Then we don't need to show the template parameter type .
Stack<int> intStack1; // stack of strings
Stack<int> intStack2 = intStack1; // OK in all versions
Stack intStack3 = intStack1; // OK since C++17
Add constructors that infer class model types :
template<typename T>
class Stack {
private:
std::vector<T> elems; // elements
public:
Stack () = default; //
Stack (T elem) : elems({std::move(elem)}) {}
...
};
Use :
Stack intStack = 80; // Stack<int> deduced since C++17
Why add Stack () = default;
In order to Stack<int> s;
This default structure does not report errors .
Deduction Guides
We can use Deduction Guides
To provide additional template parameter derivation rules , Or modify the existing template parameter inference rules .
Stack(char const*) -> Stack<std::string>;
Stack stringStack{"bottom"}; // OK: Stack<std::string> deduced since C++17
More rules and usage can be found in :Class template argument deduction (CTAD) (since C++17)
( End )
Friends can pay attention to my official account , Get the most up-to-date updates :