CPP-Templates-2nd--第十九章 萃取的实现 19.7---

 

目录

19.7 其它的萃取技术 

19.7.1 If-Then-Else

19.7.2 探测不抛出异常的操作

19.7.3 萃取的便捷性(Traits Convenience)

别名模板和萃取(Alias Templates And Traits)

变量模板和萃取(Variable Templates and Traits)

19.8 类型分类(Type Classification)

19.8.1 判断基础类型(Determining Fundamental Types)

19.8.2 判断复合类型

指针

引用

数组

指向成员的指针(Pointers to Members)

19.8.3 识别函数类型(Identifying Function Types)

19.8.4 判断 class 类型(Determining Class Types)

19.8.5 识别枚举类型(Determining Enumeration Types)

19.9 策略萃取(Policy Traits)

19.9.1 只读参数类型

19.10 在标准库中的情况


参考:GitHub - Walton1128/CPP-Templates-2nd--: 《C++ Templates 第二版》中文翻译,和原书排版一致,第一部分(1至11章)以及第18,19,20,21、22、23、24、25章已完成,其余内容逐步更新中。 个人爱好,发现错误请指正

 

19.7 其它的萃取技术 

19.7.1 If-Then-Else

在上一小节中,PlusResultT 的定义采用了和之前完全不同的实现方法,该实现方法依赖于另 一个萃取(HasPlusT)的结果。我们可以用一个特殊的类型模板 IfThenElse 来表达这一 if-then-else 的行为,它接受一个 bool 型的模板参数,并根据该参数从另外两个类型参数中 间做选择:

#ifndef IFTHENELSE_HPP
#define IFTHENELSE_HPP
// primary template: yield the second argument by default and rely on
// a partial specialization to yield the third argument
// if COND is false
template<bool COND, typename TrueType, typename FalseType>
struct IfThenElseT {
using Type = TrueType;
};
// partial specialization: false yields third argument
template<typename TrueType, typename FalseType>
struct IfThenElseT<false, TrueType, FalseType> {
using Type = FalseType;
};
template<bool COND, typename TrueType, typename FalseType>
using IfThenElse = typename IfThenElseT<COND, TrueType,
FalseType>::Type;
#endif //IFTHENELSE_HPP

下面的例子展现了该模板的一种应用,它定义了一个可以为给定数值选择最合适的整形类型 的函数:

#include <limits>
#include "ifthenelse.hpp"
template<auto N>
struct SmallestIntT {
	using Type =
		typename IfThenElseT<N <= std::numeric_limits<char> ::max(), char,
			typename IfThenElseT<N <=
			std::numeric_limits<short> ::max(), short,
				typename IfThenElseT<N <=
				std::numeric_limits<int> ::max(), int,
					typename IfThenElseT<N <=
					std::numeric_limits<long>::max(), long,
						typename IfThenElseT<N <=
						std::numeric_limits<long long>::max(), long long, //then
							void //fallback
		>::Type
		>::Type
		>::Type
		>::Type
		>::Type;
};

需要注意的是,和常规的 C++ if-then-else 语句不同,在最终做选择之前,then 和 else 分支 中的模板参数都会被计算,因此两个分支中的代码都不能有问题,否则整个程序就会有问题。

考虑下面这个例子,一个可以为给定的有符号类型生成与之对应的无符号类型的萃取。已经 有一个标准萃取(std::make_unsigned)可以做这件事情,但是它要求传递进来的类型是有 符号的整形,而且不能是 bool 类型;否则它将使用未定义行为的结果

这一萃取不够安全,因此最好能够实现一个这样的萃取,当可能的时候,它就正常返回相应 的无符号类型,否则就原样返回被传递进来的类型(这样,当传递进来的类型不合适时,也 能避免触发未定义行为)。下面这个简单的实现是不行的:

// ERROR: undefined behavior if T is bool or no integral type:
template<typename T>
struct UnsignedT {
using Type = IfThenElse<std::is_integral<T>::value
&& !std::is_same<T,bool>::value, typename std::make_unsigned<T>::type,
T>;
};

报错:
错误    C2338    static_assert failed: 'make_unsigned<T> requires that T shall be a (possibly cv-qualified) integral type or enumeration but not a bool type.'      

 

因为在实例化 UnsingedT的时候,行为依然是未定义的,编译期依然会试图从下面的 代码中生成返回类型:

typename std::make_unsigned<T>::type

为了解决这一问题,我们需要再引入一层额外的间接层,从而让 IfThenElse 的参数本身用类 型函数去封装结果:

// yield T when using member Type:
template<typename T>
struct IdentityT {
using Type = T;
};
// to make unsigned after IfThenElse was evaluated:
template<typename T>
struct MakeUnsignedT {
using Type = typename std::make_unsigned<T>::type;
};
template<typename T>
struct UnsignedT {
using Type = typename IfThenElse<std::is_integral<T>::value
&& !std::is_same<T,bool>::value,
MakeUnsignedT<T>,
IdentityT<T>
>::Type;
};

在这一版 UnsignedT 的定义中,IfThenElse 的类型参数本身也都是类型函数的实例。只不过 在最终 IfThenElse 做出选择之前,类型函数不会真正被计算。而是由 IfThenElse 选择合适的 类型实例(MakeUnsignedT 或者 IdentityT)。最后由::Type 对被选择的类型函数实例进行计 算,并生成结果 Type。

此处值得强调的是,之所以能够这样做,是因为 IfThenElse 中未被选择的封装类型永远不会 被完全实例化。

下面的代码也不能正常工作:

template<typename T>
struct UnsignedT {
using Type = typename IfThenElse<std::is_integral<T>::value
&& !std::is_same<T,bool>::value,
MakeUnsignedT<T>::Type,
T
>::Type;
};

我们必须要延后对 MakeUnsignedT使用::Type,也就是意味着,我们同样需要为 else 分支 中的 T 引入 IdentyT 辅助模板,并同样延后对其使用::Type。

在 C++标准库中有与 IfThenElseT 模板对应的模板(std::conditional<>,参见第 D.5 节)。使 用这一标准库模板实现的 UnsignedT 萃取如下:

template<typename T>
struct UnsignedT {
using Type = typename std::conditional_t<std::is_integral<T>::value
&& !std::is_same<T,bool>::value,
MakeUnsignedT<T>,
IdentityT<T>
>::Type;
};
19.7.2 探测不抛出异常的操作
template<typename T1, typename T2>
class Pair {
T1 first;
T2 second;
public:
Pair(Pair&& other)
: first(std::forward<T1>(other.first)),
second(std::forward<T2>(other.second)) {
}
};

当 T1 或者 T2 的移动操作会抛出异常时,Pair 的移动构造函数也会抛出异常。如果有一个叫 做 IsNothrowMoveConstructibleT 的萃取,就可以在 Pair 的移动构造函数中通过使用 noexcept 将这一异常的依赖关系表达出来:

Pair(Pair&& other)
noexcept(IsNothrowMoveConstructibleT<T1>::value &&
IsNothrowMoveConstructibleT<T2>::value)
: first(std::forward<T1>(other.first)),
second(std::forward<T2>(other.second))
{}

现在剩下的事情就是去实现 IsNothrowMoveConstructibleT 萃取了。我们可以直接用 noexcept 运算符实现这一萃取,这样就可以判断一个表达式是否被进行 nothrow 修饰了:

#include <utility> // for declval
#include <type_traits> // for bool_constant
template<typename T>
struct IsNothrowMoveConstructibleT
: std::bool_constant<noexcept(T(std::declval<T>()))>
{};

但是该实现还应该被继续优化,因为它不是 SFINAE 友好的

就像在第 19.4.4 节介绍的那样,在真正做计算之前,必须先对被用来计算结果的表达式的有 效性进行判断。在这里,我们要在检查移动构造函数是不是 noexcept 之前,先对其有效性 进行判断。因此,我们要重写之前的萃取实现,给其增加一个默认值是 void 的模板参数, 并根据移动构造函数是否可用对其进行偏特化:

#include <utility> // for declval
#include <type_traits> // for true_type, false_type, and
bool_constant<>
// primary template:
template<typename T, typename = std::void_t<>>
struct IsNothrowMoveConstructibleT : std::false_type
{ };
// partial specialization (may be SFINAE’d away):
template<typename T>
struct IsNothrowMoveConstructibleT<T,
std::void_t<decltype(T(std::declval<T>()))>>
: std::bool_constant<noexcept(T(std::declval<T>()))>
{};

如果在偏特化中对 std::void_t的替换有效,那么就会选择该偏特化实现,在其父类中的 noexcept(...)表达式也可以被安全的计算出来。否则,偏特化实现会被丢弃(也不会对其进 行实例化),被实例化的也将是主模板(产生一个 std::false_type 的返回值)。

C++标准库提供了与之对应的萃取 std::is_move_constructible<>,在第 D.3.2 节有对其进行介 绍。

19.7.3 萃取的便捷性(Traits Convenience)

一个关于萃取的普遍不满是它们相对而言有些繁琐,因为对类型萃取的使用通需要提供一 个::Type 尾缀,而且在依赖上下文中(dependent context),还需要一个 typename 前缀,两 者几成范式。当同时使用多个类型萃取时,会让代码形式变得很笨拙,

通过使用别名模板(alias templates)和变量模板(variable templates),可以让对产生类型 或者数值的萃取的使用变得很方便。但是也需要注意,在某些情况下这一简便方式并不使用, 我 们 依 然 要 使 用 最 原 始 的 类 模 板 。 我 们 已 经 讨 论 过 一 个 这 一 类 的 例 子 (MemberPointerToIntT),但是更详细的讨论还在后面。

别名模板和萃取(Alias Templates And Traits)

将别名模板用于类型萃取也有一些缺点:

1. 别名模板不能够被进行特化(在第 16.3 节有过提及),但是由于很多编写萃取的技术 都依赖于特化,别名模板最终可能还是需要被重新导向到类模板。

2. 有些萃取是需要由用户进行特化的,比如描述了一个求和运算符是否是可交换的萃取, 此时在很多使用都用到了别名模板的情况下,对类模板进行特化会很让人困惑。

3. 对别名模板的使用会让该类型被实例化(比如,底层类模板的特化),这样对于给定 类型我们就很难避免对其进行无意义的实例化(正如在第 19.7.1 节讨论的那样)。

对最后一点的另外一种表述方式是,别名模板不可以和元函数转发一起使用(参见第 19.3.2 节)。

由于某些历史原因,C++标准库选择了不同的命名惯例。其类型萃取会包含一个 type 类型成员,但是不会有特定的后缀(在 C++11 中为某些类型萃取引入了后缀)。从 C++14 开始,为之引入了相应的别名模板(直接生成 type),该别名模板会有一个_t 后缀

变量模板和萃取(Variable Templates and Traits)

对于返回数值的萃取需要使用一个::value(或者类似的成员)来生成萃取的结果。在这种情 况下,constexpr 修饰的变量模板提供了一种简化代码的方法。

同样由于历史原因,C++标准库也采用了不同的命名惯例。产生 result 结果的萃取类模板并 没有特殊的后缀,而且它们中的一些在 C++11 中就已经被引入进来了。在 C++17 中引入的 与之对应的变量模板则有一个_v 后缀

19.8 类型分类(Type Classification)

如果能够知道一个模板参数的类型是内置类型,指针类型,class 类型,或 者是其它什么类型,将会很有帮助。

19.8.1 判断基础类型(Determining Fundamental Types)
#include <cstddef> // for nullptr_t
#include <type_traits> // for true_type, false_type, and
bool_constant<>
// primary template: in general T is not a fundamental type
template<typename T>
struct IsFundaT : std::false_type {
};
// macro to specialize for fundamental types
#define MK_FUNDA_TYPE(T) \
template<> struct IsFundaT<T> : std::true_type { \
};
MK_FUNDA_TYPE(void)
MK_FUNDA_TYPE(bool)
MK_FUNDA_TYPE(char)
MK_FUNDA_TYPE(signed char)
MK_FUNDA_TYPE(unsigned char)
MK_FUNDA_TYPE(wchar_t)
MK_FUNDA_TYPE(char16_t)
MK_FUNDA_TYPE(char32_t)
MK_FUNDA_TYPE(signed short)
MK_FUNDA_TYPE(unsigned short)
MK_FUNDA_TYPE(signed int)
MK_FUNDA_TYPE(unsigned int)
MK_FUNDA_TYPE(signed long)
MK_FUNDA_TYPE(unsigned long)
MK_FUNDA_TYPE(signed long long)
MK_FUNDA_TYPE(unsigned long long)
MK_FUNDA_TYPE(float)
MK_FUNDA_TYPE(double)
MK_FUNDA_TYPE(long double)
MK_FUNDA_TYPE(std::nullptr_t)
#undef MK_FUNDA_TYP

主模板定义了常规情况。也就是说,通常而言 IfFundaT::value 会返回 false:

template<typename T>
struct IsFundaT : std::false_type {
static constexpr bool value = false;
};

对于每一种基础类型,我们都进行了特化,因此 IsFundaT::value 的结果也都会返回 true。 为了简单,我们定义了一个可以扩展成所需代码的宏。比如:

MK_FUNDA_TYPE(bool)

会扩展成:

template<> struct IsFundaT<bool> : std::true_type {
static constexpr bool value = true;
};

下面的例子展示了该模板的一种可能的应用场景:

#include "isfunda.hpp"
#include <iostream>
template<typename T>
void test (T const&)
{
if (IsFundaT<T>::value) {
std::cout << "T is a fundamental type" << ’\n’;}
else {
std::cout << "T is not a fundamental type" << ’\n’;
}
}
int main()
{
test(7);
test("hello");
}

其输出如下:

T is a fundamental type

T is not a fundamental type

19.8.2 判断复合类型

       复合类型是由其它类型构建出来的类型。简单的复合类型包含指针类型,左值以及右值引用 类型,指向成员的指针类型(pointer-to-member types),和数组类型。它们是由一种或者 两种底层类型构造的。Class 类型以及函数类型同样也是复合类型,但是它们可能是由任意 数量的类型组成的。在这一分类方法中,枚举类型同样被认为是复杂的符合类型,虽然它们 不是由多种底层类型构成的。简单的复合类型可以通过偏特化来区分。

指针
template<typename T>
struct IsPointerT : std::false_type { //primary template: by default
not a pointer
};
template<typename T>
struct IsPointerT<T*> : std::true_type { //partial specialization for
pointers
using BaseT = T; // type pointing to
};

C++标准库也提供了相对应的萃取 std::is_pointer<>,但是没有提供一个成员类型来描述指针 所指向的类型。

引用

左值引用:

template<typename T>
struct IsLValueReferenceT : std::false_type { //by default no lvalue
reference
};
template<typename T>
struct IsLValueReferenceT<T&> : std::true_type { //unless T is lvalue
references
using BaseT = T; // type referring to
};

右值引用:

template<typename T>
struct IsRValueReferenceT : std::false_type { //by default no rvalue
reference
};
template<typename T>
struct IsRValueReferenceT<T&&> : std::true_type { //unless T is rvalue
reference
using BaseT = T; // type referring to
};

它俩又可以被组合成 IsReferenceT<>萃取:

#include "islvaluereference.hpp"
#include "isrvaluereference.hpp"
#include "ifthenelse.hpp"
template<typename T>
class IsReferenceT
: public IfThenElseT<IsLValueReferenceT<T>::value,
IsLValueReferenceT<T>,
IsRValueReferenceT<T>
>::Type {
};

C++标准库也提供了相应的 std::is_lvalue_reference<>和 std::is_rvalue_reference<>萃取(相关 介绍请参见第 D.2.1 节),还有 std::is_reference<>(相关介绍请参见第 D.2.2 节)。同样的, 这些萃取也没有提供代表其所引用的类型的类型成员。

数组

在定义可以判断数组的萃取时,让人有些意外的是偏特化实现中的模板参数数量要比主模板多:

#include <cstddef>
template<typename T>
struct IsArrayT : std::false_type { //primary template: not an array
};
template<typename T, std::size_t N>
struct IsArrayT<T[N]> : std::true_type { //partial specialization for
arrays
using BaseT = T;
static constexpr std::size_t size = N;
};
template<typename T>
struct IsArrayT<T[]> : std::true_type { //partial specialization for
unbound arrays
using BaseT = T;
static constexpr std::size_t size = 0;
};

C++标准库提供了相应的 std::is_array<>来判断一个类型是不是数组,在第 D.2.1 节有其相关 介绍。除此之外,诸如 std::rank<>和 std::extent<>之类的萃取还允许我们去查询数组的维度 以及某个维度的大小

指向成员的指针(Pointers to Members)
template<typename T>
struct IsPointerToMemberT : std::false_type { //by default no
pointer-to-member
};
template<typename T, typename C>
struct IsPointerToMemberT<T C::*> : std::true_type { //partial
specialization
using MemberT = T;
using ClassT = C;
};

C++ 标 准 库 提 供 了 更 为 具 体 的 萃 取 , std::is_member_object_pointer<> 和 std::is_member_function_pointer<> , 详 见 第 D.2.1 节 , 还 有 在 第 D.2.2 节 介 绍 的 std::is_member_pointer<>。

19.8.3 识别函数类型(Identifying Function Types)

函数类型比较有意思,因为它们除了返回类型,还可能会有任意数量的参数。因此,在匹配 一个函数类型的偏特化实现中,我们用一个参数包来捕获所有的参数类型,就如同我们在 19.3.2 节中对 DecayT 所做的那样:

#include "../typelist/typelist.hpp"
template<typename T>
struct IsFunctionT : std::false_type { //primary template: no function
};
template<typename R, typename… Params>
struct IsFunctionT<R (Params…)> : std::true_type
{ //functions
using Type = R;
using ParamsT = Typelist<Params…>;
static constexpr bool variadic = false;
};
template<typename R, typename… Params>
struct IsFunctionT<R (Params…, …)> : std::true_type { //variadic
functions
using Type = R;
using ParamsT = Typelist<Params…>;
static constexpr bool variadic = true;
};

上述实现中函数类型的每一部分都被暴露了出来:返回类型被 Type 标识,所有的参数都被 作为 ParamsT 捕获进了一个 typelist 中(在第 24 章有关于 typelist 的介绍),而可变参数(...) 表示的是当前函数类型使用的是不是 C 风格的可变参数。

这一形式的 IsFunctionT 并不能处理所有的函数类型,因为函数类型还可以包含 const 和 volatile 修饰符,以及左值或者右值引用修饰符(参见第 C.2.1 节),在 C++17 之后, 还有 noexcept 修饰符。

因此,为了识别有限制符的函数类型,我 们需要引入一大批额外的偏特化实现,来覆盖所有可能的限制符组合(每一个实现都需要包 含 C 风格和非 C 风格的可变参数情况)。这里,我们只展示所有偏特化实现中的 5 中情况:

template<typename R, typename… Params>
struct IsFunctionT<R (Params…) const> : std::true_type {
using Type = R;
using ParamsT = Typelist<Params…>;
static constexpr bool variadic = false;
};
template<typename R, typename… Params>
struct IsFunctionT<R (Params…, …) volatile> : std::true_type {
using Type = R;
using ParamsT = Typelist<Params…>;
static constexpr bool variadic = true;
};
template<typename R, typename… Params>
struct IsFunctionT<R (Params…, …) const volatile> : std::true_type {
using Type = R;
using ParamsT = Typelist<Params…>;
static constexpr bool variadic = true;
};
template<typename R, typename… Params>
struct IsFunctionT<R (Params…, …) &> : std::true_type {
using Type = R;
using ParamsT = Typelist<Params…>;
static constexpr bool variadic = true;
};
template<typename R, typename… Params>
struct IsFunctionT<R (Params…, …) const&> : std::true_type {
using Type = R;
using ParamsT = Typelist<Params…>;
static constexpr bool variadic = true;
};

C++标准库也提供了相应的 std::is_function<>萃取

19.8.4 判断 class 类型(Determining Class Types)

不能像处理基础类型一样一一列举所有的 class 类型。相反,我们需要用一种 间接的方法来识别 class 类型,为此我们需要找出一些适用于所有 class 类型的类型或者表达 式(但是不能适用于其它类型)。有着这样的类型或者表达式之后,我们就可以使用在第 19.4 节介绍的 SFINAE 萃取技术了。

Class 中可以被我们用来识别 class 类型的最为方便的特性是:只有 class 类型可以被用于指 向成员的指针类型(pointer-to-member types)的基础。也就是说,对于 X Y::*一类的类型结 构,Y 只能是 class 类型。下面的 IsClassT<>就利用了这一特性(将 X 随机选择为 int)

#include <type_traits>
template<typename T, typename = std::void_t<>>
struct IsClassT : std::false_type { //primary template: by default no
class
};
template<typename T>
struct IsClassT<T, std::void_t<int T::*>> // classes can have
pointer-to-member
: std::true_type {
};

C++语言规则指出,lambda 表达式的类型是“唯一的,未命名的,非枚举 class 类型”。因 此在将 IsClassT 萃取用于 lambda 表达时,我们得到的结果是 true:

auto l = []{};
static_assert<IsClassT<decltype(l)>::value, "">; //succeeds

需要注意的是,int T::*表达式同样适用于 unit 类型(更具 C++标准,枚举类型也是 class 类 型)。

C++标准库提供了 std::is_class<>和 std::is_union 萃取,在第 D.2.1 节有关于它们的介绍。但是, 这些萃取需要编译期进行专门的支持,因为目前还不能通过任何核心的语言技术(standard core language techniques)将 class 和 struct 从 union 类型中分辨出来。

19.8.5 识别枚举类型(Determining Enumeration Types)

目前通过我们已有的萃取技术还唯一不能识别的类型是枚举类型。我们可以通过编写基于 SFINAE 的萃取来实现这一功能,这里首先需要测试是否可以像整形类型(比如 int)进行显 式转换,然后依次排除基础类型,class 类型,引用类型,指针类型,还有指向成员的指针 类型(这些类型都可以被转换成整形类型,但是都不是枚举类型)。但是也有更简单的方法, 因为我们发现所有不属于其它任何一种类型的类型就是枚举类型,这样就可以像下面这样实  现该萃取:

template<typename T>
struct IsEnumT {
static constexpr bool value = !IsFundaT<T>::value
&& !IsPointerT<T>::value &&
!IsReferenceT<T>::value
&& !IsArrayT<T>::value &&
!IsPointerToMemberT<T>::value
&& !IsFunctionT<T>::value &&
!IsClassT<T>::value;
};

C++标准库提供了相对应的 std::is_enum<>萃取,

19.9 策略萃取(Policy Traits)

到目前为止,我们例子中的萃取模板被用来判断模板参数的特性:它们代表的是哪一种类型, 作用于该类型数值的操作符的返回值的类型,以及其它特性。这一类萃取被称为特性萃取 (property traits)。

最为对比,某些萃取定义的是该如何处理某些类型。我们称之为策略萃取(policy traits)。 这里会对之前介绍的策略类(policy class,我们已经指出,策略类和策略萃取之间的界限并 不青霞)的概念进行回顾,但是策略萃取更倾向于是模板参数的某一独有特性(而策略类却 通常和其它模板参数无关)。

虽然特性萃取通常都可以被实现为类型函数,策略萃取却通常将策略包装进成员函数中

19.9.1 只读参数类型

这一类问题通常应当用策略萃取模板(一个类型函数)来处理:该函 数将预期的参数类型 T 映射到最佳的参数类型 T 或者是 T const&。

#ifndef RPARAM_HPP
#define RPARAM_HPP
#include "ifthenelse.hpp"
#include <type_traits>
template<typename T>
struct RParam {
using Type = IfThenElse<(sizeof(T) <= 2*sizeof(void*)
&& std::is_trivially_copy_constructible<T>::value
&& std::is_trivially_move_constructible<T>::value),
T,
T const&>;
};
#endif //RPARAM_HPP
#include "rparam.hpp"
#include <iostream>
class MyClass1 {
public:
MyClass1 () {
}
MyClass1 (MyClass1 const&) {
std::cout << "MyClass1 copy constructor called\n";}
};
class MyClass2 {
public:
MyClass2 () {
}
MyClass2 (MyClass2 const&) {
std::cout << "MyClass2 copy constructor called\n";
}
};
// pass MyClass2 objects with RParam<> by value
template<>
class RParam<MyClass2> {
public:
using Type = MyClass2;
};
#include "rparam.hpp"
#include "rparamcls.hpp"
// function that allows parameter passing by value or by reference
template<typename T1, typename T2>
void foo (typename RParam<T1>::Type p1, typename RParam<T2>::Type p2)
{ …
}
int main()
{
MyClass1 mc1;
MyClass2 mc2;
foo<MyClass1,MyClass2>(mc1,mc2);
}

不幸的是,PParam 的使用有一些很大的缺点。第一,函数的声明很凌乱。第二,可能也是 更有异议的地方,就是在调用诸如 foo()一类的函数时不能使用参数推断,因为模板参数只  出现在函数参数的限制符中。因此在调用时必须显式的指明所有的模板参数。

一个稍显笨拙的权宜之计是:使用提供了完美转发的 inline 封装函数(inline wrapper function),但是需要假设编译器将省略 inline 函数:

#include "rparam.hpp"
#include "rparamcls.hpp"
// function that allows parameter passing by value or by reference
template<typename T1, typename T2>
void foo_core (typename RParam<T1>::Type p1, typename RParam<T2>::Type
p2)
{ …
}
// wrapper to avoid explicit template parameter passing
template<typename T1, typename T2>
void foo (T1 && p1, T2 && p2)
{
foo_core<T1,T2>(std::forward<T1>(p1),std::forward<T2>(p2));
}
int main()
{
MyClass1 mc1;
MyClass2 mc2;
foo(mc1,mc2); // same as foo_core<MyClass1,MyClass2> (mc1,mc2)
}

19.10 在标准库中的情况

因此,如果你需要类型萃取,我们建议在可能的情况下都尽量使用由 C++标准库提供的萃取。

C++标准库也定义了一些策略和属性萃取:

 类模板 std::char_traits 被 std::string 和 I/O stream 当作策略萃取使用。

 为 了 将 算 法 简 单 的 适 配 于 标 准 迭 代 器 的 种 类 , 标 准 库 提 供 了 一 个 很 简 单 的 std::iterator_traits 属性萃取模板。

 模板 std::numeric_limits 作为属性萃取模板也会很有帮助。

 最后,为标准库容器类型进行的内存分配是由策略萃取类处理的(参见 std::shared_ptr 的实现)。从 C++98 开始,标准库专门为了这一目的提供了 std::allocator 模板。从 C++11 开始,标准库引入了 std::allocator_traits 模板,这样就能够修改内存分配器的策略或者 行为了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值