团队做网站的收获,温州做网站定制,wordpress怎么搜站点,网页设计资源目录
1.简介
2.C20的std::type_identity
3.使用 type_identity
3.1.阻止参数推导
3.1.1.模板参数推导过程中的隐式类型转换
3.1.2.强制显式实例化
3.2.阻止推断指引
3.3.类型保持
3.4.满足一些稀奇古怪的语法
4.示例
5.总结 1.简介 std::type_identity 是 C17 引入的…目录
1.简介
2.C20的std::type_identity
3.使用 type_identity
3.1.阻止参数推导
3.1.1.模板参数推导过程中的隐式类型转换
3.1.2.强制显式实例化
3.2.阻止推断指引
3.3.类型保持
3.4.满足一些稀奇古怪的语法
4.示例
5.总结 1.简介 std::type_identity 是 C17 引入的一个实用工具用于确保类型别名保持其引用的完整性。在某些模板元编程的场景中尤其是在与类型萃取type traits和完美转发perfect forwarding相关的场景中保持类型的“原样”传递是非常重要的。 std::type_identity 是一个简单的模板它定义了一个别名 type该别名简单地重新声明了其模板参数类型。但重要的是它不会修改或“破坏”传递给它的类型。 在 C17 之前要实现这样的效果通常需要一些技巧比如使用结构体的模板特化或复杂的继承层次结构。但是std::type_identity 使得这个过程变得更加简单和直观。 我们用一个例子来说明这个问题尽管这个例子有点微不足道
template class T
T Add(T a, T b) {return a b;
}Add(4.2, 1); //错误 尽管将整数与浮点数累加并返回浮点数是 Add 函数应该支持的运算但是编译器却在这里报错因为在参数推导的时候它根据第一个参数推导出 T 是 double 类型但是根据第二个参数推导出 T 是 int 类型这产生了矛盾于是编译器罢工了。 解决这个问题的方法很简单比如我们可以谢绝自动推导用显式实例化Explicit instantiation的方式只是每次调用的时候会麻烦一点
foodouble(4.2, 1); 当然更常用的实践是借助 C 的非推导语境non-deduced contexts规避不希望的参数推导比如下面这种 identity 惯用法
template class T
struct identity {using type T;
};template class T
T Add(T a, typename identityT::type b) {return a b;
}//相当于 double Add(double a, double b)
foo(4.2, 1); //OK T 被推导为 double 根据 identity 的定义identityT::type 其实就是 T为什么加上这个多此一举的东西就 OK 了这并不是什么黑魔法它只是借助了模板参数推导规则中最常用的一种非推导语境即 对于模板参数中出现的嵌套类型表达式域解析运算符::左边的嵌套名称说明符如果是个限定性说明符Qualified identifiers则该嵌套名称说明符不参与模板参数推导。 所以用了 identity 大法之后::type 左边的 identityT 就不参与模板参数推导T 就是根据第一个参数推导出的 doubleidentityT::type 就也是 double 了。
2.C20的std::type_identity C 20 的 type traits 增加了一个 type_identity其作用和上一节中自定义的 identityT 一样只是不用重复发明轮子了。直接使用 type_identity 的代码是这个样子的
template class T
T Add(T a, typename std::type_identityT::type b) {return a b;
} C 还提供了一个别名
template class T
using type_identity_t typename type_identityT::type; 使用 type_identity_tT 的代码更简单一点
template class T
T Add(T a, std::type_identity_tT b) {return a b;
}
3.使用 type_identity
3.1.阻止参数推导
3.1.1.模板参数推导过程中的隐式类型转换 其实建立非推导语境的常用目的是让一个模板参数的类型依赖另一个模板参数的推导结果这种非推导语境的建立还会带来一些意想不到的效果。比如这个例子
template typename ...args_t
void func(std::functionvoid(args_t...) function_, args_t ...args){/// do something here
}void func2(std::functionvoid(int) function_, int args) {/// do something here
}void test() {func([](int a){ }, 1); //编译错误func2([](int a){ }, 1); //没有问题
} 代码中的 func 和 func2 函数的作用相当于一个调用器Invoker区别就是 func 是个函数模板而 func2 是个普通函数。test 函数中调用 func 会导致编译错误调用 func2 确实正常的。func2 能正常调用说明从 lambda 到 std::function 的隐式转换是没有问题的那为什么 func 就不能转换呢 原因就是在参数推导的时候是不考虑隐式类型转换的func 是函数模板它的第一个参数类型 std::function 依赖于对模板参数 args_t 的推导结果推导出来的 std::functionvoid(args_t...) 无法与实参传入的 lambda 表达式类型进行匹配导致推导最终失败实际上并没有产生一个类似
void func(std::functionvoid(int) function_, int)
的推导结果在随后的重载决议时虽然允许隐式转换但是因为没有一个上述结果能与之进行转换最终的结果就是编译失败错误原因是没有一个 func 的实例能匹配 lambda 表达式的调用。func2 能调用成功是因为 func2 是普通函数此处会进行隐式类型转换。 此时如果让 std::functionvoid(args_t...) 不参与推导那么它就不需要与实参传入的 lambda 表达式进行匹配也就是不会导致推导错误就能得到上面的推导结果于是在随后的重载决议的时候就能通过隐式类型转换完成函数调用。此时就需要用 type_identity 建立非推导语境了我们将 func 的设计改成这样就可以了
template typename... args_t
void func(std::type_identity_tstd::functionvoid(args_t...) function_, args_t ...args) {/// do something here
}
3.1.2.强制显式实例化 利用 type_identity还可以在设计上要求用户在使用函数模板的时候必须显式指定模板参数比如这个例子
template typename U, typename V
void foo(std::type_identity_tU u, V v) { ... }foodouble(5.9, 6); //编译正确
fooint(5.9, 6); // 编译正确此处发生隐式类型转换
foo(5.3, 6.2); //错误 foo 函数从设计上强制用户必须指定第一个参数的类型目的可能是想允许第一个参数的隐式类型转换也可能是其他目的总之使用 type_identity 可以达到这种效果使用 foo 函数的用户必须显式指定第一个参数的类型。
3.2.阻止推断指引 C 17 引入了一个新的语言特性就是 CTAD借助于隐式或显式的推断指引类模板的模板参数也支持自动推导。但是隐式的类型推导有可能会产生错误的结果比如这个 smart_pointer 类的设计
template class T
class smart_pointer {
public:smart_pointer(T* object);//...
} 借助于隐式推断指引用户可以写出这样的代码不需要显式指定模板参数 T
Widget* widget{/* ... */};
smart_pointer ptr{widget}; 但是问题是T* 代表的指针无法区别 object 是单个对象的指针还是数组因为数组在函数调用的时候也会退化成指针所以自动推导出来的类型有可能是错误的比如这样的代码
Widget widget[N];
smart_pointer ptr{widget}; 此时推导类型 T 仍然是 Widget我们希望的是 Widget[]。 借助于 type_identity我们可以阻止这种隐式的推断指引强制用户指定正确的模板参数类型。我们将 smart_pointer 的构造函数修改一下
smart_pointer(std::type_identity_tT* object); 这样上述代码就会产生错误用户必须这样使用才能正确编译这也是我们期望的正确结果
Widget widget[N];
smart_pointerWidget[] ptr{widget};
3.3.类型保持 type_identity_tT 本质上还是 T所以可以被用在一些需要短暂记忆并保持类型的场合。资料 [5] 是 Timur Doumler 为推动 type_identity 进入标准而做的提案它给出了几个利用 type_identity 的类型保持功能使得 type_identity 可以作为其他元函数Meta function的实现基础比如我们可以模仿标准库实现一个 remove_const 的元函数type traits
template typename T
struct remove_const : std::type_identityT {};template typename T
struct remove_constT const : std::type_identityT {};
大家可能会有疑问为什么不直接写成这样
template typename T
struct remove_const : T {};template typename T
struct remove_constT const : T {};
如果写成第二种形式则这样的断言会失败
static_assert(std::is_same_vremove_constint const, int); 因为通过 remove_constT const 或 remove_constT 得到的类型都是 remove_constT不是 T。使用 type_identity我们就可以借助于它的 type 类型保持得到原始的 T这样的断言就是成功的
static_assert(std::is_same_vremove_const2int const::type, int); 因为通过 remove_const2T::type 可以得到原始类型 T而不是 remove_const2T那不是我们要的结果。
3.4.满足一些稀奇古怪的语法 type_identity 的其他用法还包括满足一些稀奇古怪的语法形式比如语法上我们要创建一个临时数组但是直接写 T[]{} 语法形式上就是错误的因为编译器不能确定 [] 的左边是标识符还是类型。但是用 type_identity 中转一下编译器就确定知道 [] 左边是个类型
templatetypename T
void Print(T v[]) { ... }templatetypename T
void Process(T t) {Print(std::type_identity_tT[]{ 1,2,3 }); //编译器能正确理解//PrintInt(T[]{ 1,2,3 }); //语法错误[] 左边不能确定是类型
}
4.示例
代码如下
#include iostream
#include type_traits
#include functionaltemplate typename T
struct type_identity
{using type T;
};template typename T
using type_identity_t typename type_identityT::type;template typename... args_t
void func_wrapped(type_identity_tstd::functionvoid(args_t...) function_,args_t ...args)
{std::cout typeid(function_).name(): typeid(function_).name() std::endl;std::cout typeid(std::functionvoid(args_t...)).name(): typeid(std::function void(args_t...)).name() std::endl;std::cout std::is_same::value std::is_same std::functionvoid(args_t...),type_identity_tstd::functionvoid(args_t...)::value std::endl std::endl;// do something here
}void test()
{std::cout __FUNCTION__ std::endl;
}int main()
{std::cout std::boolalpha;func_wrapped([](int a) { }, 1);func_wrapped(test);return 0;
}
输出
typeid(function_).name(): St8functionIFviEE
typeid(std::functionvoid(args_t...)).name(): St8functionIFviEE
std::is_same::value truetypeid(function_).name(): St8functionIFvvEE
typeid(std::functionvoid(args_t...)).name(): St8functionIFvvEE
std::is_same::value true
5.总结 希望大家能够有所收获笔者水平有限。成文之处难免有理解谬误之处欢迎大家多多讨论指教。
推荐文章阅读
std::type_identity