C++雾中风景17:模板的非推断语境与std::type_identity
乍一看这个标题很玄乎,但是其实这只是涉及一个很简单的CPP的模板推导的知识点。
笔者近期进行CPP开发工作时,在编译时遇到了如下的模板类型的推断错误:note: candidate template ignored: deduced conflicting types for parameter T (long long vs. long int)。通过一番梳理之后总结成文,希望对大家有所帮助。
1.非推断语境
众所周知,函数模板的使用是C++编译期进行类型推导的过程。通过分析源代码之中函数实参的类型,进一步推断出调用的函数参数的类型,从而自动生成对应的函数,来达到精简代码逻辑的效果。
而所谓非推断语境呢?则是模板的类型不参与模板实参推导,取而代之地使用可在别处推导或显式指定的模板实参。
单看上述文字可能很难理解,咱们直接看代码就能明白了。
2.举个栗子
我们先来看看下面的一段简单的代码:
template<typename T>
struct TestTemplate {
T t;
};
template<typename T>
T add(TestTemplate<T>& test, T val) {
return test.t + val;
}
int main() {
TestTemplate<long> test_template{100};
return add(test_template, 10);
}
在进行编译的时候出现如下的报错:
note: template argument deduction/substitution failed:
note: deduced conflicting types for parameter 'T' ('long int' and 'int')
通过gcc的编译报错我们可以看出,这里出现了错误的模板推断问题。模板函数add在进行类型推断时出现了冲突,在同一个函数中,模板类型T
被同时推断为long
与int
。
我们来分析一下模板推断的流程。
- 首先,参数
test_template
的类型为TestTempalate<long>
, 它作为add
函数的第一个参数传入,此时T
的类型被推导为了long
。 - 接着,参数
val
的类型为int
, 它作为add
函数的第二个参数传入,而此时由于13为int
类型,所以T
被推导为int
类型。
正是因为这样,在add
函数进行模板推导的过程之中,两个参数test
与val
同时参与了模板类型的推导,导致出现了上述的问题。
我们可以尝试将add
函数的调用改为如下:add(test_template, 10l)
。此时val
也作为参数T也被推导为long
类型,则编译不再报错。
3. 利用非推断语境解决问题
显然,上面的代码我们希望编译器支持将int
类型自动推导为long
,而不要出现恼人的报错。那我们就需要利用非推断语境来解决问题了,让val
的类型不要参与到类型推导过程之中来,那么问题就解决了。
模板的非推断语境出现比较复杂,有需要的可以参考cppreference部分的详细解释。我们将利用第一种,也是最常见的非推断语境来解决上文提到的问题。
The nested-name-specifier (everything to the left of the scope resolution operator ::)
简单来说就是::
左侧作用域的类型,不参与模板类型的推导。
所以上述代码改为如下代码,就可以规避原先的问题了。
template<typename T>
struct TestTemplate {
T t;
};
template<typename T> struct identity { typedef T type; };
template<typename T>
T add(TestTemplate<T>& test, typename identity<T>::type val) {
return test.t + val;
}
int main() {
TestTemplate<long> test_template{1000};
return add(test_template, 10);
}
这里我们新添加了类型identity
, 并利用typename identity<T>::type
规避了模板的类型推断过程,从而让val
的类型推断直接利用了test
参数的类型推断结果,所以此时val
的类型为long
,模板类型推断也就不再出错了。
正是因为非推断语境在模板推断中会被使用,所以C++20提供了新的trait
:
std::type_identity
与std::type_identity_t
来帮助我们解决上述的问题。它们的实现与功能与上面展示的identity
一致,都是利用模板的非推断语境来规避类型推断不同导致的编译失败问题。
4.小结
C++的一些模板推断的问题常常让人抓狂,很多时候gcc给出的一长串报错很容易劝退萌新。本篇聊了聊笔者实际在开发中遇到的模板推断问题出发,一步步分析报错,希望大家对解决编译问题有耐心,并擅用搜索引擎,功力必不唐捐。(当然,更新的C++标准也给我们解决问题的武器库添砖加瓦,多多学习才是正道,日常一念:C++20好~~~)
希望大家能够有所收获,笔者水平有限。成文之处难免有理解谬误之处,欢迎大家多多讨论,指教。
5.参考资料
C++雾中风景17:模板的非推断语境与std::type_identity的更多相关文章
- C++语言基础(20)-模板的非类型参数
一.在函数模板中使用非类型参数 #include <iostream> using namespace std; template<class T> void Swap(T & ...
- scala学习手记17 - 容器和类型推断
关于scala的类型推断前面已经提到过多次.再来看一下下面这个例子: import java.util._ var list1: List[Int] = new ArrayList[Int] var ...
- 【模板】 非旋转treap
模板:luogu P3369 [模板]普通平衡树 code: #include <cstdio> #include <cstdlib> const int MAX_N=1000 ...
- java基础17 模板模式
1.模版模式 解决某类事物的步骤有些是固定的,有些会发生改变的,那么这个时候我们可以为这一类事物提供一个模版代码,从而提高效率. 2.模版模式的步骤 1.先写出解决该类事物的其中一种解决方案; ...
- 【转】 史上最详尽的平衡树(splay)讲解与模板(非指针版spaly)
ORZ原创Clove学姐: 变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结 ...
- 【模板】非旋转Treap
Treap,也叫做树堆,是指有一个随机附加域满足堆的性质的二叉搜索树. 如果一棵二叉搜索树插入节点的顺序是随机的,那我们得到的二叉搜索树在大多数情况下是平衡的,期望高度是log(n). 但有些情况下我 ...
- c++11-17 模板核心知识(三)—— 非类型模板参数 Nontype Template Parameters
类模板的非类型模板参数 函数模板的非类型模板参数 限制 使用auto推断非类型模板参数 模板参数不一定非得是类型,它们还可以是普通的数值.我们仍然使用前面文章的Stack的例子. 类模板的非类型模板参 ...
- 现代C++之理解模板类型推断(template type deduction)
理解模板类型推断(template type deduction) 我们往往不能理解一个复杂的系统是如何运作的,但是却知道这个系统能够做什么.C++的模板类型推断便是如此,把参数传递到模板函数往往能让 ...
- C++17尝鲜:类模板中的模板参数自动推导
模板参数自动推导 在C++17之前,类模板构造器的模板参数是不能像函数模板的模板参数那样被自动推导的,比如我们无法写 std::pair a{1, "a"s}; // C++17 ...
随机推荐
- 后端程序员之路 36、Apache Kafka
Apache Kafkahttp://kafka.apache.org/ Kafka,很容易就联想到<海边的卡夫卡>,文艺程度和Casablanca有得一拼.Kafka是一个分布式消息系统 ...
- 后端程序员之路 10、gbdt(Gradient Boosting Decision Tree)
1.GbdtModelGNode,含fea_idx.val.left.right.missing(指向left或right之一,本身不分配空间)load,从model文件加载模型,xgboost输出的 ...
- C++共享数据保护机制
下面随笔说明C++共享数据保护机制. 共享数据的保护 对于既需要共享.又需要防止改变的数据应该声明为常类型(用const进行修饰). 对于不改变对象状态的成员函数应该声明为常函数. (1)常类型 ①常 ...
- pytorch中多个loss回传的参数影响示例
写了一段代码如下: import torch import torch.nn as nn import torch.nn.functional as F class Test(nn.Module): ...
- 你不知道的Scheduled定时任务骚操作
目录 一.什么是定时任务 二.项目依赖 三.注解式定时任务 3.1 cron 3.2 fixedDelay 3.3 fixedDelayString 3.4 fixedRate 3.5 fixedRa ...
- 【Android笔记】Thread类中关于join()方法的源码分析
1.join()方法的作用: 例如有一个线程对象为Thread1,在main()方法中调用Thread1.join()方法可使得当前线程(即主线程)阻塞,而执行Thread1线程. 2.源码分析(以上 ...
- Java 面向对象 01
面向对象·一级 面向对象思想概述 * A:面向过程思想概述 * 第一步 * 第二步 * B:面向对象思想概述 * 找对象(第一步,第二步) * C:举例 * 买煎饼果子 ...
- 2020年12月-第02阶段-前端基础-CSS基础选择器
CSS选择器(重点) 理解 能说出选择器的作用 id选择器和类选择器的区别 1. CSS选择器作用(重点) 如上图所以,要把里面的小黄人分为2组,最快的方法怎办? 很多, 比如 一只眼睛的一组,剩下的 ...
- C#类中的成员
@ 目录 字段 属性 方法 构造函数 类和对象的简单解释 创建类和对象 类中成员的归属问题 字段 字段的声明与声明变量类似,可以添加访问修饰符,通常情况下字段设置为私有的,然后定义属性对字段的读写进行 ...
- python3 获取博彩网站页面下所有域名(批量)
已有的域名信息 详细实现过程如下 #!/usr/bin/env python # -*- coding:utf-8 -*- import requests from bs4 import Beauti ...