一、前言
- C++标准库几乎每样东西都以template为根基。为了更多地支持template编程,彼岸准哭提供了template通用工具,协助应用程序开发人员和程序库作者
- Type Trait,由TR1引入,在C++11中被大幅度扩展,定义出因type而异的行为。它们可被用来针对type优化代码,以便提供特别能力
- 其他工具如reference和function wrapper,也为编程带来若干帮助
二、Type Trait的目的
- 目的:提供一种用来处理type属性的方法。它是个template,可在编译期根据一个或多个template实参产出一个type或value
演示案例1(std::is_pointer<>)
#include <type_traits> template< typename T> void foo(const T& val) { if ( std::is_pointer<T>::value) { std:: cout << "foo() called for a pointer" << std:: endl; } else { std:: cout << "foo() called for a value" << std:: endl; } } int main() { int num; int *p = # foo(num); foo(p); }
- 运行结果如下:
- 代码解释:
- 这里的std::is_pointer<>属于一个traits类。如果传入的参数类型为指针类型,其返回std::true_type;如果传入的参数类型不是指针类型,其返回std::false_type。然后std::true_type::value或std::false_type::value返回相对应的true或false
- 第一个foo()调用传入的参数为非指针类型
演示案例2(std::true_type、std::false_type)
- 我们修改演示案例1中的foo()函数,让其打印传入的元素的值,于是设计了下面的代码:
- 但是这是错误的,编译不通过
- 假设val不是指针类型,而代码中却有*val的操作,这显然是错误的
template< typename T> void foo(const T& val) { std:: cout << ( std::is_pointer<T>::value ? *val : val) << std:: endl; }
- 如果想要达到上面的目的,可以借助std::true_type和std::false_type来完成。代码如下:
#include <type_traits> //如果是指针,调用这个 template< typename T> void foo_impl(const T& val,std::true_type) { std:: cout << "foo() called for a pointer:" << *val << std:: endl; } //如果不是指针,调用这个 template< typename T> void foo_impl(const T& val, std::false_type) { std:: cout << "foo() called for a value:" << val << std:: endl; } template< typename T> void foo(const T& val) { foo_impl(val, std::is_pointer<T>()); } int main() { int num = 10; int *p = # foo(num); foo(p); }
- 直接调用foo_impl()函数也是可以的。例如:
int main() { int num = 10; int *p = # foo_impl(num, std::is_pointer< int>()); foo_impl(p, std::is_pointer< int*>()); }
演示案例3(std::is_integral<>,针对整数类型的弹性重载)
- 例如现在我们有一批重载函数,一部分是针对于整数类型的,一部分是针对于浮点数类型的。例如:
void foo(short); void foo(unsigned short); void foo(int); void foo(float); void foo(double); void foo(long double);
- 上面的代码有很大的缺点:这样做重复工作很多,代码比较冗余,并且如果加入了新数据类型那么还需要重新定义新的foo()函数
- 一种做法是使用trait机制提供的模板类,例如此处使用std::integral<>模板。定义的代码如下:
//针对于整数类型设计的 template< typename T> void foo_impl(T val, std::true_type); //针对于浮点数类型设计的 template< typename T> void foo_impl(T val, std::false_type); template< typename T> void foo(T val) { //通过is_integral萃取类型 //如果T为整数类型,std::is_integral返回std::true_type;否则返回std::false_type foo_impl(val, std::is_integral<T>()); }
演示案例4(std::common_type<>,处理通用类型)
- 假设我们有个函数来比较两个值的最小值,并将最小值进行返回,如果T1和T2的数据类型不一致,那么返回值该如何定义哪?
- 我们可以借助std::common_type<>解决这个问题。例如:
- 如果传入的两个实参都是int,或传入的都是long,或者传入的一个是int一个是long,那么std::common_type<>返回int,因此下面的min的返回值为int类型
- 如果传入的参数一个是string而另一个是字符串字面常量,那么下面的min的返回值为std::string
template< typename T1, typename T2> typename std::common_type<T1, T2>:: type min(const T1& x, const T2& y);
- 使用std::common_type<>的前提是,两个实参它们有共同的数据类型。前提是程序员自己保证的(看下面的实现原理)
- std::common_type<>的实现原理如下:
- 其内部使用?:运算符,直接返回T1的数据类型。因此上面不论min()的T2参数属于什么类型,其只返回T1所表示的数据类型
- 其内部使用了一个std::declval<>模板,特属于trait的一种,其根据传入的类型提供一个值,但不去核算它(最终返回一个该值的rvalue reference)
- 然后再使用decltype关键字导出表达式的类型
- 通过上面的comm_type<>就可以找出一个共同类型,如果找不到,可以使用common_type<>的重载版本(这正是chrono程序库的作为,使它得以结合duration;详情见后面的chrono程序库介绍)
三、Type Trait的分类
- Type trait大多数定义于<type_traits>头文件中,有些定义在别的头文件中(下面会注释)
①类型判断式
- 下图列出了针对于所有类型都使用的trait:
- 下图列出了针对于class类型都使用的trait
- std::true_type、std::false_type:
- 上面的类型的返回值是std::true_type或std::false_type
- std::true_type和std::false_type都是std::integral_constant的特化,因此它们相应的value成员可以返回true或false。如下图所示:
- 一些注意事项:
- bool和所有character类型(char、char16_t、char32_t、wchar_t)都属于整数类型
- std::nullptr_t为基础数据类型
- 上面大部分都是单参数形式的,但并非全部都是
- 一个“指向const类型”的非常量pointer或reference,其本身并不是一个常量(见下面演示案例)
- 用以检验copy和move语义的那些trait,只检验是否相应的表达式为可能。例如,一个“带有copy构造函数(接受常量实参)但没有move构造函数”的类型,仍然是move constructible
- is_nothrow..type trait特别被用来阐述noexcept异常声明
- 下面是is_const<>的演示案例:
std:: cout << boolalpha; std:: cout << "is_const<int>::value " << is_const< int>::value << std:: endl; std:: cout << "is_const<const volatile int>::value " << is_const< const volatile int>::value << std:: endl; std:: cout << "is_const<int* const>::value " << is_const< int* const>::value << std:: endl; std:: cout << "is_const<const int*>::value " << is_const< const int*>::value << std:: endl; std:: cout << "is_const<const int&>::value " << is_const< const int&>::value << std:: endl; std:: cout << "is_const<int[3]>::value " << is_const< int[ 3]>::value << std:: endl; std:: cout << "is_const<const int[3]>::value " << is_const< const int[ 3]>::value << std:: endl; std:: cout << "is_const<int[]>::value " << is_const< int[]>::value << std:: endl; std:: cout << "is_const<const int[]>::value " << is_const< const int[]>::value << std:: endl;
②用以检验类型关系的Trait
- 下图列出的type trait可以检查类型之间的关系,包括检查class type提供了哪一种构造函数和哪一种赋值操作等等
- is_assignable<>的使用注意事项:
- 注意,基本数据类型(例如int)可以表现出lvalue或是rvalue,因此你不能够直接赋值,例如“42=77”,这是错误的。因此is_assignable<>第一个类型如果是一个nonclass类型,永远会获得false_type
- 如果是class类型,以其寻常类型作为第一类型是可以的,因为存在一个有趣的旧规则:你可以调用“类型为class”的rvalue的成员函数。例如:
std:: cout << boolalpha; std:: cout << "is_assignable<int,int>::value " << is_assignable< int, int>::value << std:: endl; std:: cout << "is_assignable<int&,int>::value " << is_assignable< int&, int>::value << std:: endl; std:: cout << "is_assignable<int&&,int>::value " << is_assignable< int&&, int>::value << std:: endl; std:: cout << "is_assignable<long&,int>::value " << is_assignable< long&, int>::value << std:: endl; std:: cout << "is_assignable<int&,void*>::value " << is_assignable< int&, void*>::value << std:: endl; std:: cout << "is_assignable<void*,int>::value " << is_assignable< void*, int>::value << std:: endl; std:: cout << "is_assignable<const char*,std::string>::value " << is_assignable< const char*, std:: string>::value << std:: endl; std:: cout << "is_assignable<std::string,const char*>::value " << is_assignable< std:: string, const char*>::value << std:: endl;
- 下面是is_constructible<>的演示案例:
std:: cout << boolalpha; std:: cout << "is_constructible<int>::value " << is_constructible< int>::value << std:: endl; std:: cout << "is_constructible<int,int>::value " << is_constructible< int, int>::value << std:: endl; std:: cout << "is_constructible<long,int>::value " << is_constructible< long, int>::value << std:: endl; std:: cout << "is_constructible<int,void*>::value " << is_constructible< int, void*>::value << std:: endl; std:: cout << "is_constructible<void*,int>::value " << is_constructible< void*, int>::value << std:: endl; std:: cout << "is_constructible<const char*,std::string>::value " << is_constructible< const char*, std:: string>::value << std:: endl; std:: cout << "is_constructible<std::string,const char*>::value " << is_constructible< std:: string, const char*>::value << std:: endl; std:: cout << "is_constructible<std::string,const char*,int,int>::value " << is_constructible< std:: string, const char*, int, int>::value << std:: endl;
- std::use_allocator<>被定义在<memory>头文件中
③类型修饰符
- 下图列出的trait允许你改动类型
- 使用规则:
- 如果想要为某一类型添加一个属性,前提是该属性尚未存在
- 如果想要为某一类型移除一个属性,前提是该属性已经存在
- 下面是一些演示案例:
- 类型const int&可被降级或扩展。例如:
- 一些注意事项:
- 一个“指向某常量类型”的reference本身并不是常量,所以你不可以移除其常量性
- add_pointer<>必然包含使用remove_reference<>
- 然后make_signed<>和make_unsigned<>要求实参若非整数类型就必须是枚举类型,bool除外,所以如果你传入reference会导致不明确行为
- add_value_reference<>把一个rvalue reference转换为一个lvalue reference,然而add_rvalue_reference<>并不能把一个lvalue reference转换为一个rvalue reference(类型保持不变)。因此,必须这么做才能将一个lvalue转换为一个rvalue reference:
④其他trait
- 下图列出了其余所有type trait。它们用来查询特殊属性、检查类型关系、或提供更复杂的类型变换
- decay<>允许你讲“以by value传入”的类型T转换为其相应类型。以此方式,它转换array和function类型称为pointer,把lvalue转换为rvalue——其中包括移除const和volatile
- common_type<>为所有被传入的类型提供一个共同类型(它可以有1个、2个或更多个类型实参)
- 下面是一些演示案例:
四、Reference Wrapper(外覆器)
- 声明于<functional>中的一些类型:
- std::reference_wrapper<>:可以将传值调用改为传reference调用
- std::ref():可以将类型T隐式转换为T&
- std::cref():可以将类型T隐式转换为const T&
- 演示案例:
-
template<
typename T>
-
void foo(T val) { val++; }
-
-
int main()
-
{
-
int num1 =
1, num2 =
1;
-
-
foo(num1);
-
std::
cout <<
"num1:" << num1 <<
std::
endl;
-
-
foo(
std::ref(num2));
//改为T&调用
-
std::
cout <<
"num2:" << num2 <<
std::
endl;
-
}
- 这些特性被C++标准库运用于各个地方,例如:
- make_pair()用此特性,于是能够创建一个pair<> of references
- make_tuple()用此特性,于是能够创建一个tuple<> of references
- Binder用此特性,于是能够绑定reference
- Thread用此特性,于是能够以by reference形式传递实参
- 注意事项:class reference_wrapper使你得以使用reference作为最高级对象,例如作为array或STL容器的元素类型(演示案例参阅:https://blog.csdn.net/qq_41453285/article/details/105485576)
五、Function Type Wrapper(外覆器)
- std::function<>:
- std::function<>声明于<functional>,提供多态外覆器,可以概括functional pointer记号
- 这个模板允许你把可调用对象当做最高级对象
- std::function<>在另外一篇文章单独介绍过:https://blog.csdn.net/qq_41453285/article/details/95184168
- 演示案例:
-
int func(int x, int y)
-
{
-
return x + y;
-
}
-
-
int main()
-
{
-
std::
vector<
std::function<
void(
int,
int)>> tasks;
-
tasks.push_back(func);
-
tasks.push_back([](
int x,
int y) {
return x + y; });
-
-
for (
std::function<
void(
int,
int)> f : tasks) {
-
f(
33,
66);
-
}
-
}
- 如果使用member function,那么必须将“调用它们”的那个对象作为参数1进行传递。例如:
-
class C {
-
public:
-
void memfunc(int x, int y) {}
-
};
-
-
int main()
-
{
-
std::function<
void(
const C&,
int,
int)> mf;
-
mf = &C::memfunc;
-
mf(C(),
42,
77);
-
}
- 这个东西的另一个应用:声明某个函数返回一个lambda(详情见《C++标准库》P31)
- 注意:执行一个函数调用,却没有标的物可调用,将会抛出std::bad_function_call异常。例如:
转载:https://blog.csdn.net/qq_41453285/article/details/105454812
查看评论