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 ...
随机推荐
- 正则表达式匹配${key}并在Java中使用
1.正则表达式匹配${key} \$\{([a-z]+)\} 能够匹配字符串中以${key}形式的文本(其中key为小写应为字母) .*\$\{([a-z]+)\}.* 可以用来检测文本中是否有${k ...
- Java 8 中Sort排序原理:
总的来说,java中Arrays.sort使用了两种排序方法,快速排序和优化的合并排序.Collections.sort方法底层就是调用的Arrays.sort方法. 快速排序主要是对那些基本类型数据 ...
- Newbe.Claptrap 框架入门,第一步 —— 开发环境准备
Newbe.Claptrap 框架依托于一些关键性的基础组件和一些可选的辅助组件.本篇我们来介绍一下如何准备一个开发环境. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如 ...
- 基于solarflare的openonload技术以TCPDirect方法加速epoll
[前言]基于solarflare的onload模式加速,官方文档给出TCPDirect模式可以实现从300ns到30ns的延迟缩减.我们需要测试在我们的交易模型框架中他的延时,有人给出了tcpdire ...
- C++核心篇
C++核心编程 本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓. 1 内存分区模型 C++程序在执行时,将内存大方向划分为4个区域 代码区:存放函数体的二进制代码,由操作系统 ...
- OAID 文档与获取DemoAPK
OAID 查看器 接入 OAID SDK 的 Demo 工程 完整项目地址 Github 文档与 Demo 下载 release 提供 APK 下载 支持设备见 说明文档 常见问题见 F&Q文 ...
- vue封装一个弹框组件
这是一个提示框和对话框,例: 这是一个组件 eject.vue <template> <div class='kz-cont' v-show='showstate'> &l ...
- 剑指 Offer 35. 复杂链表的复制
剑指 Offer 35. 复杂链表的复制 Offer_35 题目详情 方法一 可以使用一个HashMap来存储旧结点和新结点的映射. 这种方法需要遍历链表两遍,因为需要首先知道映射关系才能求出next ...
- Codeforces 682C Alyona and the Tree
题目链接:http://codeforces.com/problemset/problem/682/C 分析:存图,用dfs跑一遍,详细见注释 1 #include<iostream> 2 ...
- Linux安全模型中的3A
3A Authentication : 认证 验证用户身份 Authorization : 授权 不同用户设置不同权限 Accouting | Audition : 审计 Linux 验证用户身份 U ...