本文是阅读C++ templates: the complete guide 2nd
的笔记。
模板参数不会被推导的情况 1 2 3 4 5 6 7 8 9 10 #include <iostream> template <typename RT, typename T1, typename T2>RT max (T1 a, T2 b) { return a > b ? a : b; } int main () { int a = 2 ; double b = 3.14 ; std::cout << ::max (a, b) << std::endl; }
1 2 3 4 prog.cpp:3:4: note: template argument deduction/substitution failed: prog.cpp:9:25: note: couldn't deduce template parameter ‘RT’ std::cout << ::max(a, b) << std::endl; ^
上面的例子中,RT
的类型是T1
or T2
,编译器无法推断:
In cases when there is no connection between template parameters(e.g. RT) and call parameters(e.g. a and b) , the compiler can’t take the template parameters into account and also can’t deduce it.
RT
与函数形参无关时,编译器不会去推断 RT
的类型。
编译器不是推断不出RT
,而是根本不会去推断RT
。把代码改成这样,编译器也不会去推断RT
:
1 2 3 4 5 6 7 8 9 10 #include <iostream> template <typename RT, typename T>RT max (T a, T b) { return a > b ? a : b; } int main () { int a = 2 ; int b = 3 ; std::cout << ::max (a, b) << std::endl; }
尽管我们一眼可以看出RT
肯定等于T
,但编译器不会去推断。
正确示范
使用auto
1 2 3 4 5 6 7 8 9 10 #include <iostream> template <typename T1, typename T2>auto max (T1 a, T2 b) { return a > b ? a : b; } int main () { int a = 2 ; double b = 3.14 ; std::cout << ::max (a, b) << std::endl; }
auto
会在编译期进行推断,例如a为int,b为double
,在进行a>b
比较时,a
会隐式转换为double
,那么auto
会被推断为double
.
显示指定
1 2 3 4 5 6 7 8 9 10 #include <iostream> template <typename RT, typename T1, typename T2>RT max (T1 a, T2 b) { return a > b ? a : b; } int main () { int a = 2 ; double b = 3.14 ; std::cout << ::max <double >(a, b) << std::endl; }
type trait 类型萃取
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> #include <type_traits> template <typename T1, typename T2, typename RT = std::common_type_t <T1,T2>>RT max (T1 a, T2 b){ return a > b ? a : b; } int main () { int a = 2 ; double b = 3.14 ; std::cout << ::max <double >(a, b) << std::endl; }
std::common_type_t<>
会返回多个类型的公共类型。例如std::common_type_t<int,double>
等于double
,因为可以隐式转换int
为double
从而统一类型。
Concepts 在模板下添加requires
1 2 3 4 5 6 7 #include <concepts> template <typename T>requires std::totally_ordered<T> T my_max (T a, T b) { return a > b ? a : b; }
可以尝试去掉以下代码中的requires
,看看报错信息有多么令人头疼。加上requires
,报错信息更精确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <vector> #include <string> #include <concepts> class Person {private : std::string name; public : template <typename STR> requires std::is_convertible_v<STR,std::string> explicit Person (STR&& n) : name(std::forward<STR>(n)){ std::cout << "TMPL-CONSTR for " << name << "\n" ; } }; int main () { Person p (123 ) ; }
常见的约束:
概念(Concept)
用途
示例
std::integral<T>
T
必须是整数类型(int
, short
等)
requires std::integral<T>
std::floating_point<T>
T
必须是浮点类型(float
, double
)
requires std::floating_point<T>
std::copyable<T>
T
可拷贝(有拷贝构造函数和赋值运算符)
requires std::copyable<T>
std::movable<T>
T
可移动(有移动构造函数和赋值运算符)
requires std::movable<T>
std::equality_comparable<T>
T
支持 ==
和 !=
requires std::equality_comparable<T>
std::totally_ordered<T>
T
支持 <
, <=
, >
, >=
requires std::totally_ordered<T>
std::ranges::range<T>
T
是范围(有 begin()
和 end()
)
requires std::ranges::range<T>
std::input_iterator<T>
T
是输入迭代器
requires std::input_iterator<T>
std::invocable<F, Args...>
F
可调用,参数为 Args...
requires std::invocable<F, int, double>
Defining Concepts
检查成员函数是否存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <concepts> #include <vector> #include <string> #include <iostream> template <typename T>requires requires (T obj) { obj.size (); obj.clear (); } void reset (T& obj) { obj.clear (); } int main () { std::vector<int > v1{1 ,2 }; reset (v1); std::cout << v1.size () << std::endl; int a = 1 ; reset (a); return 0 ; }
检查运算符
1 2 3 4 5 6 7 template <typename T>requires requires (T a, T b) { { a + b } -> std::convertible_to<T>; } T add (T a, T b) { return a + b; }
Alias Template 1 2 3 4 5 6 7 8 9 template <typename T>class MyContainer { ...... } template <typename T>using MyIterator = typename MyContainer<T>::iterator;MyIterator<int > pos;
Nontype Template Parameters 模板参数不一定是type
,也可以是constant integral values
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <array> #include <cassert> template <typename T, int MaxSize>class myStack {private : std::array<T,MaxSize> elem; int num; public : myStack (); void push (const T& _elem) ; void pop () ; T top () ; int size () ; }; template <typename T, int MaxSize>myStack<T,MaxSize>::myStack (): num (0 ){} template <typename T, int MaxSize>void myStack<T,MaxSize>::push (const T& _elem){ assert (num < MaxSize); elem[num++] = _elem; } template <typename T, int MaxSize>void myStack<T, MaxSize>::pop (){ assert (num>0 ); --num; } template <typename T, int MaxSize>int myStack<T, MaxSize>::size (){ return num; } template <typename T, int MaxSize>T myStack<T, MaxSize>::top (){ assert (num > 0 ); return elem[num-1 ]; }
Variadic Templates 通过variadic Templates
实现可变参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> void print () {}template <typename T, typename ... Types>void print (T first, Types... args) { std::cout << first << std::endl; print (args...); } int main () { print (1 , 2.0 , "Hello" ); }
注意:要在模板前定义一个终止函数void print(){}
,否则模板展开时会反复调用print(arg...)
导致爆栈。
实现and_all
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> bool and_all () { return true ; } template <typename T>bool and_all (T one) { return one; } template <typename T, typename ... Types>bool and_all (T first, Types... args) { return first && and_all (args...); } int main () { std::cout << and_all (1 ,1 ,1 ,1 ,1 ,0 ) << std::endl; std::cout << and_all (1 ) << std::endl; }
Fold Expressions
折叠类型
语法
展开示例(参数包为 a, b, c
)
一元左折叠
( ... op args )
((a op b) op c)
一元右折叠
( args op ... )
(a op (b op c))
二元左折叠
( init op ... op args )
(((init op a) op b) op c)
二元右折叠
( args op ... op init )
(a op (b op (c op init)))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> template <typename ... Types>bool and_all (Types... args) { return (args && ...); } template <typename ... Types>auto add_all (Types... args) { return (0 + ... + args); } int main () { std::cout << and_all (1 ,1 ,1 ,1 ,1 ,0 ) << std::endl; std::cout << add_all (1 , 3.14 , 2u ) << std::endl; }
不能写成 return (args + ...)
; 防止无参数调用,例如add_all()
括号不能少,不能写成return 0 + ... + args
(0 + ... + args)
就是( init op ... op args )
的形式
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> template <typename ... Types>void print_all (Types... args) { ( (std::cout << args << "\n" ), ... ); } int main () { print_all (1 , "hello" , 3.14 , "world" ); }
( (std::cout << args << "\n"), ... )
是一元右折叠表达式.
args
拼成了一个新的new_args
= (std::cout << args << "\n")
,new_args
中有四个变量,分别是(std::cout << 1 << "\n")
, (std::cout << "hello" << "\n")
, (std::cout << 3.14 << "\n")
, (std::cout << "world" << "\n")
(new_args , ...)
一元右折叠,op
= ,
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> template <typename T1, typename ... TN>constexpr bool isHomogeneous (T1, TN...) { return (std::is_same<T1,TN>::value && ...); } int main () { std::cout << isHomogeneous (1 , 2 , "hello" ) << std::endl; std::cout << isHomogeneous (1 , 2 , 3 ) << std::endl; }
Generic Template Parameter & Reference Collasing & Perfect Forwarding add_all
这样写更好:
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> template <typename ... Args>auto add_all (Args&&... args) { return (0 + ... + std::forward<Args>(args)); } int main () { int x = 6 ; std::cout << add_all (1 , x, 2 , 3.14 , 'a' ) << std::endl; }
Generic Template Parameter
:使用Args&&...
能够同时处理lvalue
和rvalue
Reference Collasing
: int&&&
等价于int&
, int&&&&
-> int&&
Perfect Forwarding
: 完美转发,不改变左值右值属性
当add_all
被传入rvalue
例如3.14
时,Args
推断为float
, 完美转发后为float&& 3.14
当add_all
被传入lvalue
例如x
时,Args
推断为int&
, 完美转发后为int&&& x
= int& x
一个使用完美转发前后的性能对比实验:link
typename
keyword1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <string> #include <vector> #include <tuple> template <typename T>void printcoll (T const & coll) { typename T::const_iterator pos; typename T::const_iterator end (coll.end()) ; for (pos = coll.begin (); pos != end; ++pos){ std::cout << *pos << std::endl; } } int main () { std::vector<int > v1{1 , 2 , 3 }; std::vector<std::string> v2{"hello" , "world" }; printcoll (v1); printcoll (v2); }
Disable Templates with enable_if_t<>
语法:
1 2 3 4 template <typename T>std::enable_if_v< bool_expr, type > foo () { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <type_traits> template <typename T>std::enable_if_t <std::is_integral_v<T>, void > print (T value) { std::cout << "Integer: " << value << std::endl; } template <typename T> std::enable_if_t <std::is_floating_point_v<T>, void > print (T value) { std::cout << "Floating: " << value << std::endl; } int main () { print (42 ); print (3.14f ); print ("hello" ); return 0 ; }
可以使用默认模板参数+enable_if_t
来对模板参数进行约束:
1 2 3 4 5 6 7 8 9 10 11 #include <type_traits> template <typename T, typename = std::enable_if_t <std::is_integral_v<T>>>void foo () { std::cout << "T is integral" << std::endl; } int main () { foo <int >(); foo <double >(); return 0 ; }
第一个模板参数是T
,第二个模板参数typename = std::enable_if_t<std::is_integral_v<T>>
, 通过第二个模板参数的推导来限制第一个模板参数T
的类型
如果T
是整数类型,模板展开为
1 tempalte<typename T, typename = void >
第二个模板参数用不上,只是为了去约束T
的。
concept 个人觉得concept
相较于enable_if
更加简洁清晰。但是concept
在C++20
进入标准,可能老代码还是得用enable_if
.
1 2 3 4 5 6 template <typename STR>requires std::is_convertible_v<STR,std::string>PerSon (STR&& n) :name(std::forward<STR>(n)) {...... }