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

2023-09-17 16:02:06

 

目录

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 模板,这样就能够修改内存分配器的策略或者 行为了。

更多推荐

Docker

目录Docker什么是Docker?Docker和虚拟机的差异Docker架构Docker的安装卸载(可选)安装docker启动docker配置镜像加速Docker什么是Docker?Docker是一个快速交付应用、运行应用的技术,具备下列优势:将程序及其依赖、运行环境一起打包为一个镜像,可以在任意Linux操作系统下

Spring面试题3:说一说MVC框架的底层实现

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点面试官:说一说MVC框架的底层实现MVC(Model-View-Controller)是一种软件设计模式,常被用于构建用户界面和应用程序的架构。MVC框架的底层实现可以分为以下几个部分:模型(Model):模型

【GIS】地理坐标系WGS84、GCJ-02、BD-09、GCS2000

地理坐标系又可分为参心坐标系和地心坐标系,常见的参心坐标系北京54、西安80,常见的地心坐标系有WGS84、GCJ-02、BD-09、GCS2000地心坐标系WGS84(WorldGeodeticSystem1984)WGS84是为GPS全球定位系统建立的坐标系统,是世界上第一个统一的地心坐标系,因此也被称为大地坐标系

TS中 说说数组在TypeScript中是如何工作的?

在TypeScript中,数组(Array)是一种用于存储多个值的数据结构,它有一些特定的特性和操作方法。数组在TypeScript中的工作方式与JavaScript中的数组类似,但通过类型系统提供了更强大的类型检查和推断。下面是关于TypeScript数组的一些重要信息:1.类型检查和推断:TypeScript可以根

【Linux】进程控制,进程替换

1.进程创建fork函数初识在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。#include<unistd.h>pid_tfork(void);返回值:自进程中返回0,父进程返回子进程id,出错返回-1进程调用fork,当控制转移到内核中的fork代码后,内

BD个人总结

Flink01.水位线(watermark)用来度量时间,基于事件时间,数据的时间戳,用来衡量事件时间进展的标记。具体实现上,水位线可以看作一条特殊的数据记录,是插入到数据流中的一个标记点,主要内容就是一个时间戳,用来指示当前的事件时间。水位线也是数据流的一部分,随着数据一起流动,在不同任务之间传输。有序流中的水位线:

递归学习——记忆化搜索

目录​编辑一,概念和效果二,题目1.斐波那契数1.题目2.题目接口3.解题思路2.不同的路径1.题目2.题目接口3.解题思路3.最长增长子序列1.题目2.题目接口3.解题思路4.猜数字游戏II1.题目2.题目接口3.解题思路总结:一,概念和效果记忆化搜索可以说是带备忘录的递归,实现这个算法的目的便是减少递归时对同一个节

JS【filter过滤器】的用法

在JavaScript中,filter()是一个高阶函数,它是数组(Array)的一部分,可用于创建一个新数组,其中包含通过所提供函数实现的测试的所有元素。filter()函数的语法如下:letnewArray=arr.filter(callback(element[,index,[array]])[,thisArg]

用tkinter+selenium做一个CSDN热榜爬虫

文章目录UI设计函数封装功能实现自从学会了分析热榜,就是CSDN热榜分析,每天都要爬下来分析一下热榜都在干什么。但脚本运行到底还是不方便,所以接下来就想办法将其做成一个带有界面的热榜爬虫UI设计做一个热榜爬虫的交互式界面,只需要两个按钮外加两个信息框就足够了,所以布局极其简单classTestSTL:def__init

数据融合的并行计算

1、数据融合的算法数据融合的算法当中,需要对每一个格点i进行逐个计算,公式如下2、出现的问题但是随着背景场的空间分辨率的提高,格点数急剧增加。如空间分辨率为0.01°的话,那么15°✖15°的空间范围内就有1500✖1500个格点。那么在进行逐个格点计算的过程中,就非常耗时间。3、编程我先按照逐点计算并赋值给DataA

布隆过滤器(Bloom Filter)

一,布隆过滤器介绍布隆过滤器(BloomFilter)是一个很长的二进制向量(位图BitMap)和一系列随机映射函数(Hash函数)。它是一种数据结构,可以判断一个元素一定不在集合中或可能存在于集合中。优点:相比于传统的list、set、map等数据结构,它更高效、占用空间更少。缺点:存在误判率二,布隆过滤器原理布隆过

热文推荐