一、前言 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);
}
C++(标准库):08---Type Trait和Type Utility(type_traits库) 这里的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);
}
C++(标准库):08---Type Trait和Type Utility(type_traits库) 直接调用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的数据类型不一致,那么返回值该如何定义哪? C++(标准库):08---Type Trait和Type Utility(type_traits库) 我们可以借助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关键字导出表达式的类型 C++(标准库):08---Type Trait和Type Utility(type_traits库) 通过上面的comm_type<>就可以找出一个共同类型,如果找不到,可以使用common_type<>的重载版本(这正是chrono程序库的作为,使它得以结合duration;详情见后面的chrono程序库介绍) 三、Type Trait的分类 Type trait大多数定义于<type_traits>头文件中,有些定义在别的头文件中(下面会注释) ①类型判断式 C++(标准库):08---Type Trait和Type Utility(type_traits库) C++(标准库):08---Type Trait和Type Utility(type_traits库) std::true_type、std::false_type: 上面的类型的返回值是std::true_type或std::false_type std::true_type和std::false_type都是std::integral_constant的特化,因此它们相应的value成员可以返回true或false。如下图所示: C++(标准库):08---Type Trait和Type Utility(type_traits库) 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异常声明 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;
C++(标准库):08---Type Trait和Type Utility(type_traits库) ②用以检验类型关系的Trait 下图列出的type trait可以检查类型之间的关系,包括检查class type提供了哪一种构造函数和哪一种赋值操作等等 C++(标准库):08---Type Trait和Type Utility(type_traits库) 注意,基本数据类型(例如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;
C++(标准库):08---Type Trait和Type Utility(type_traits库) 下面是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;
C++(标准库):08---Type Trait和Type Utility(type_traits库) std::use_allocator<>被定义在<memory>头文件中 ③类型修饰符 C++(标准库):08---Type Trait和Type Utility(type_traits库) 如果想要为某一类型添加一个属性,前提是该属性尚未存在 如果想要为某一类型移除一个属性,前提是该属性已经存在 C++(标准库):08---Type Trait和Type Utility(type_traits库) C++(标准库):08---Type Trait和Type Utility(type_traits库) 一个“指向某常量类型”的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: C++(标准库):08---Type Trait和Type Utility(type_traits库) ④其他trait 下图列出了其余所有type trait。它们用来查询特殊属性、检查类型关系、或提供更复杂的类型变换 C++(标准库):08---Type Trait和Type Utility(type_traits库) decay<>允许你讲“以by value传入”的类型T转换为其相应类型。以此方式,它转换array和function类型称为pointer,把lvalue转换为rvalue——其中包括移除const和volatile common_type<>为所有被传入的类型提供一个共同类型(它可以有1个、2个或更多个类型实参) 下面是一些演示案例: C++(标准库):08---Type Trait和Type Utility(type_traits库) 四、Reference Wrapper(外覆器) 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++(标准库):08---Type Trait和Type Utility(type_traits库) make_pair()用此特性,于是能够创建一个pair<> of references make_tuple()用此特性,于是能够创建一个tuple<> of references Binder用此特性,于是能够绑定reference Thread用此特性,于是能够以by reference形式传递实参 注意事项:class reference_wrapper使你得以使用reference作为最高级对象,例如作为array或STL容器的元素类型(演示案例参阅 五、Function Type Wrapper(外覆器) std::function<>声明于<functional>,提供多态外覆器,可以概括functional pointer记号 这个模板允许你把可调用对象当做最高级对象 std::function<>在另外一篇文章单独介绍过 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异常。例如: