当前位置: 首页 > news >正文

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

相关文章:

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

目录 19.7 其它的萃取技术 19.7.1 If-Then-Else 19.7.2 探测不抛出异常的操作 19.7.3 萃取的便捷性&#xff08;Traits Convenience&#xff09; 别名模板和萃取&#xff08;Alias Templates And Traits) 变量模板和萃取&#xff08;Variable Templates and Traits&…...

python 采用selenium+cookies 获取登录后的网页

百度网页由于需要登陆手机短信验证。比较麻烦 这里我采用先人工登录百度账号&#xff0c;然后将百度账号的相关cookies保存下来 然后采用selenium动态登录网页 整体代码如下 from selenium import webdriverimport timeoptions webdriver.ChromeOptions()options.add_argu…...

【测试开发】答疑篇 · 什么是软件测试

【测试开发】答疑篇 文章目录 【测试开发】答疑篇1. 生活中的测试2. 什么是软件测试3. 为什么要有测试/没有测试行不行4. 软件测试和软件开发的区别5. 软件测试和软件调试之间的区别6. 软件测试的岗位7. 优秀测试人员具备的素质 【测试开发】答疑篇 软件不一定是桌面应用&#…...

深入解析顺序表:揭开数据结构的奥秘,掌握顺序表的精髓

&#x1f493; 博客主页&#xff1a;江池俊的博客⏩ 收录专栏&#xff1a;数据结构探索&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅C语言进阶之路&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f525;编译环境&#xff1a;Visual Studio 2022&#x1f38…...

数据风险量化评估方案

一、企业面临数据安全的痛点 1、企业缺少清晰的数据安全意识 各部门重视度不够&#xff0c;缺少主动数据安全管控意识。数据安全管控架构不清晰&#xff0c;职责划分不明确。对数据安全管控认识不全面、不深刻。工作人员对于所持有的数据缺乏概念&#xff0c;导致数据的价值无…...

EasyAVFilter代码示例之将视频点播文件转码成HLS(m3u8+ts)视频点播格式

以下是一套完整的视频点播功能开发源码&#xff0c;就简简单单几行代码&#xff0c;就可以完成原来ffmpeg很复杂的视频点播转码调用流程&#xff0c;而且还可以集成在自己的应用程序中调用&#xff0c;例如java、php、cgo、c、nodejs&#xff0c;不需要再单独一个ffmpeg的进程来…...

day-50 代码随想录算法训练营(19)动态规划 part 11

123.买卖股票的最佳时机||| 分析&#xff1a;只能买卖两次&#xff0c;就是说有五个状态&#xff1a; 没有买过第一次买入第一次卖出第二次买入第二次卖出 思路&#xff1a;二维数组&#xff0c;记录五个状态 1.dp存储&#xff1a;dp[i][1] 第一次买入 dp[i][2] 第一次卖…...

自定义权限指令与防止连点指令

1.权限指令 // 注册一个全局自定义权限指令 v-permission Vue.directive(permission, {inserted: function(el, binding, vnode) {const {value} binding; // 指令传的值// user:edit:phone,sysData:sampleconst permissions [user:edit:address, sysData:entrust, sysData:…...

UE5、CesiumForUnreal实现瓦片坐标信息图层效果

文章目录 1.实现目标2.实现过程2.1 原理简介2.2 cesium-native改造2.3 CesiumForUnreal改造2.4 运行测试3.参考资料1.实现目标 参考CesiumJs的TileCoordinatesImageryProvider,在CesiumForUnreal中也实现瓦片坐标信息图层的效果,便于后面在调试地形和影像瓦片的加载调度等过…...

PostgreSQL执行计划

1. EXPLAIN命令 1)PostgreSQL中EXPLAIN命令的语法格式: postgres# \h explain Command: EXPLAIN Description: show the execution plan of a statement Syntax: EXPLAIN [ ( option [, ...] ) ] statement EXPLAIN [ ANALYZE ] [ VERBOSE ] statementwhere option can be…...

【2023 睿思芯科 笔试题】~ 题目及参考答案

文章目录 1. 题目 & 答案单选题编程题问题1&#xff1a;解析1&#xff1a;问题2&#xff1a;解析2&#xff1a; 声明 名称如标题所示&#xff0c;希望大家正确食用&#xff08;点赞转发评论&#xff09; 本次笔试题以两种形式考察的&#xff0c;分别是&#xff1a;选择题&a…...

Java手写AVL树

Java手写AVL树 1. AVL树实现思路原理 为了解释AVL树的实现思路原理&#xff0c;下面使用Mermanid代码表示该算法的思维导图&#xff1a; #mermaid-svg-ycH8kKpzVk2HWEby {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid…...

运维自动化:提高效率的秘诀

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…...

C++设计模式_05_Observer 观察者模式

接上篇&#xff0c;本篇将会介绍C设计模式中的Observer 观察者模式&#xff0c;和前2篇模板方法Template Method及Strategy 策略模式一样&#xff0c;仍属于“组件协作”模式。Observer 在某些领域也叫做 Event 。 文章目录 1. 动机&#xff08; Motivation&#xff09;2. 代码…...

github网站打不开,hosts文件配置

首先获取github官网的ip地址&#xff0c; 打开cmd&#xff0c;输入ping github.com 配置&#xff1a; #github 140.82.114.4 github.com 199.232.69.194 github.global.ssl.fastly.net 185.199.108.153 assets-cdn.github.com 185.199.110.153 assets-cdn.github.com 185.199…...

总结PCB设计的经验

一般PCB基本设计流程如下&#xff1a;前期准备->PCB结构设计->PCB布局->布线->布线优化和丝印->网络和DRC检查和结构检查->制版。: :   第一&#xff1a;前期准备。这包括准备元件库和原理图。“工欲善其事&#xff0c;必先利其器”&#xff0c;要做出一…...

HCIE-HCS规划设计搭建

1、相关术语 1、等价路由 等价路由&#xff08;Equal-cost routing&#xff09;是一种网络路由策略&#xff0c;用于在网络中选择多个具有相同路由度量&#xff08;路由距离或成本&#xff09;的最佳路径之一来转发数据流量。 当存在多个路径具有相同的路由度量时&#xff0c;…...

c语言输出杨辉三角

#include<stdio.h> int main() {int x 0; //表示杨辉三角的的大小int y 1;printf("请输入x的值: ");scanf("%d", &x);for (int i 0; i < x; i) {for (int j 0; j < i; j) {if (j 0 || i 0) {y 1;}else {y y * (i - j 1) / j;}pri…...

性能测试-持续测试及性能测试建设(22)

什么是持续测试? 持续测试定义为:在软件交付流水线中执行自动化测试的过程,目的是获得关于预发布软件业务风险的即时反馈。 完成持续测试,我们还是需要回到定义中,它有3个关键词:软件交付流水线、自动化测试、即时反馈。 首先,持续测试需要具备一条完整的流水线,其代表…...

嵌入式C 语言中的三块技术难点

​ C 语言在嵌入式学习中是必备的知识&#xff0c;甚至大部分操作系统都要围绕 C 语言进行&#xff0c;而其中有三块技术难点&#xff0c;几乎是公认级别的“难啃的硬骨头”。 今天就来带你将这三块硬骨头细细拆解开来&#xff0c;一定让你看明白了。 0x01 指针 指针是公认…...

【斗破年番】紫研新形象,萧炎终成翻海印,救援月媚,三宗决战

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析斗破年番。 斗破苍穹年番动画更新了&#xff0c;小医仙帅气回归&#xff0c;萧炎紫妍成功进入山谷闭关苦修&#xff0c;美杜莎女王守护没多久&#xff0c;就因蛇人族求救离开。从官方公布的最新预告来看&#xff0c;萧炎紫…...

差分方程模型:国民总收入(GDP)的乘数-加速数模型

【背景知识-凯恩斯经济增长模型】 凯恩斯(John M.Keynes)建立了著名的国民经济增长模型。令Y表示国民总收入&#xff0c;C表示总消费&#xff0c;E为总支出&#xff0c;I表示投资&#xff0c;G为政府的投入&#xff08;如基建等&#xff09;。那么有 【6.1】 其中&#xff0…...

【C语言】指针和数组笔试题解析(1)

指针是C语言的灵魂&#xff0c;他的玩法多种多样&#xff0c;这篇文章带来指针的笔试题详解&#xff0c;可以帮助我们更好的理解与巩固指针的知识 目录 预备知识&#xff1a;题目&#xff1a;一维数组&#xff1a;二维数组&#xff1a; 题目比较多&#xff0c;但切记戒骄戒躁&a…...

Vue中组件的三种注册方式

组件的注册 1.全局注册&#xff1a; 在全局注册中&#xff0c;你需要确保在 Vue 根实例之前导入并注册组件。通常&#xff0c;你会在入口文件&#xff08;例如 main.js&#xff09;中执行这些操作。 // main.jsimport Vue from vue; import App from ./App.vue;// 导入全局组…...

docker 和k8s 入门

docker 和k8s 入门 本文是云原生的学习记录&#xff0c;可以参考以下文档 k8s https://www.yuque.com/leifengyang/oncloud 相关视频教程可参考如下 https://www.bilibili.com/video/BV13Q4y1C7hS?p2&vd_source0882f549dac54045384d4a921596e234 相对于公有云&#x…...

基于Yolov8的交通标志牌(TT100K)识别检测系统

1.Yolov8介绍 Ultralytics YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本。YOLOv8是一种尖端的、最先进的&#xff08;SOTA&#xff09;模型&#xff0c;它建立在先前YOLO成功基础上&#xff0c;并引入了新功能和改进&#xff0c;以进一步提升性能和灵活…...

使用Python编写一个多线程的12306抢票程序

国庆长假即将到来&#xff0c;大家纷纷计划着自己的旅行行程。然而&#xff0c;对于很多人来说&#xff0c;抢购火车票人们成了一个令人头疼的问题。12306网站的服务器经常因为流量高而崩溃&#xff0c;导致抢票变得越来越严重异常困难。 首先&#xff0c;让我们来了解一下1230…...

DT Paint Effects工具(三)

管 分支 使用细枝 叶 力 使用湍流 流动画 渲染全局参数 建造盆栽植物...

SpringBoot整合Mybatis

目录 &#xff08;1&#xff09;引入依赖 &#xff08;2&#xff09;编写Mapper接口 &#xff08;3&#xff09;编写Mapper映射文件 &#xff08;4&#xff09;编写yml配置文件 &#xff08;5&#xff09;编写测试类 &#xff08;1&#xff09;引入依赖 <dependency>…...

Java后端使用POST请求向mysql中插入Json数据的问题

1.后端请求正常 但数据表中value没有值 原因 json数据属性不符合spring解析格式&#xff0c;json属性名称的大写字母不符合spring要求 以下为为错误示范 1 Test 以大写字母开头&#xff0c; 2 tTest 小写字母开头&#xff0c;但是第二个字母是大写解决方案 实体类属性加上Jso…...

谷歌做英文网站/女教师遭网课入侵直播录屏曝光i

课程内容是完整的&#xff0c;只是目录收集和编号的有点乱&#xff0c;大家下载后的都是正常的&#xff0c;不用担心(28)\\02-跟着江哥狂虐H5跨平台开发系列-认识HTML-第一次更新\\视频&#xff1b;目录中文件数:9个├─(28) 07-HTML作用(掌握)-李南江.mp4├─(29) 08-HTML发展…...

网站建设费和网站维护费的区别/珠海企业网站建设

sdown和odown转换机制 sdown和odown两种失败状态 sdown是主观宕机&#xff0c;就一个哨兵如果自己觉得一个master宕机了&#xff0c;那么就是主观宕机 odown是客观宕机&#xff0c;如果quorum数量的哨兵都觉得一个master宕机了&#xff0c;那么就是客观宕机 sdown达成的条件很简…...

c 能用来做网站吗/百度地图广告投放

2010年7月份&#xff0c;NASA和Rackspace公司将Openstack开源。到今天&#xff0c;Openstack马上六周岁了。而6周岁正是孩子上小学一年级的时候&#xff0c;Openstack是否也准备好上小学了呢&#xff1f; Openstack在刚开始的3年受到了整个市场的热捧&#xff0c;很多国际大牌不…...

wordpress手动搬家问题/优秀营销软文100篇

描述 输出一个整数序列中与指定数字相同的数的个数。 输入 输入包含2行&#xff1a; 第1行为N和m&#xff0c;表示整数序列的长度(N < 100)和指定的数字&#xff0c; 中间用一个空格分开&#xff1b; 第2行为N个整数&#xff0c;整数之间以一个空格分开。 输出 输出为N…...

专业烟台房产网站建设/百度推广代理

通常word文件可以通过另存为html/htm的方式把图片从内容中拆分出来&#xff0c;网上一搜一大堆。 上述操作主要是针对.doc文件进行操作才能得到原图&#xff0c;现在我有.docx文件&#xff0c;使用这种方式就只能得到文档中被编辑过的图片&#xff0c;并且编辑过的图片拿出来会…...

做暧暧网站免费/官网首页入口百度

褚时健“烟王”到“橙王”&#xff1b;顾雏军“十大经济人物”到“帝国崩塌”&#xff1b;郑俊怀“乳业教父”到“争议不断”&#xff1b;还有冯鑫由“暴风主宰”变成“身陷风暴”。 从“红塔山”到“暴风”&#xff0c;1999到2019&#xff0c;相似的一幕跨越20年依旧在上演。…...