乍一看这个标题很玄乎,但是其实这只是涉及一个很简单的CPP的模板推导的知识点。

笔者近期进行CPP开发工作时,在编译时遇到了如下的模板类型的推断错误:note: candidate template ignored: deduced conflicting types for parameter T (long long vs. long int)。通过一番梳理之后总结成文,希望对大家有所帮助。

1.非推断语境

众所周知,函数模板的使用是C++编译期进行类型推导的过程。通过分析源代码之中函数实参的类型,进一步推断出调用的函数参数的类型,从而自动生成对应的函数,来达到精简代码逻辑的效果。

而所谓非推断语境呢?则是模板的类型不参与模板实参推导,取而代之地使用可在别处推导或显式指定的模板实参。

单看上述文字可能很难理解,咱们直接看代码就能明白了。

2.举个栗子

我们先来看看下面的一段简单的代码:

  1. template<typename T>
  2. struct TestTemplate {
  3. T t;
  4. };
  5. template<typename T>
  6. T add(TestTemplate<T>& test, T val) {
  7. return test.t + val;
  8. }
  9. int main() {
  10. TestTemplate<long> test_template{100};
  11. return add(test_template, 10);
  12. }

在进行编译的时候出现如下的报错:

  1. note: template argument deduction/substitution failed:
  2. note: deduced conflicting types for parameter 'T' ('long int' and 'int')

通过gcc的编译报错我们可以看出,这里出现了错误的模板推断问题。模板函数add在进行类型推断时出现了冲突,在同一个函数中,模板类型T被同时推断为longint

我们来分析一下模板推断的流程。

  • 首先,参数test_template的类型为TestTempalate<long>, 它作为add函数的第一个参数传入,此时T的类型被推导为了long
  • 接着,参数val的类型为int, 它作为add函数的第二个参数传入,而此时由于13为int类型,所以T被推导为int类型。

正是因为这样,在add函数进行模板推导的过程之中,两个参数testval同时参与了模板类型的推导,导致出现了上述的问题。

我们可以尝试将add函数的调用改为如下:add(test_template, 10l)。此时val也作为参数T也被推导为long类型,则编译不再报错。

3. 利用非推断语境解决问题

显然,上面的代码我们希望编译器支持将int类型自动推导为long,而不要出现恼人的报错。那我们就需要利用非推断语境来解决问题了,让val的类型不要参与到类型推导过程之中来,那么问题就解决了。

模板的非推断语境出现比较复杂,有需要的可以参考cppreference部分的详细解释。我们将利用第一种,也是最常见的非推断语境来解决上文提到的问题。

  1. The nested-name-specifier (everything to the left of the scope resolution operator ::)

简单来说就是::左侧作用域的类型,不参与模板类型的推导。

所以上述代码改为如下代码,就可以规避原先的问题了。

  1. template<typename T>
  2. struct TestTemplate {
  3. T t;
  4. };
  5. template<typename T> struct identity { typedef T type; };
  6. template<typename T>
  7. T add(TestTemplate<T>& test, typename identity<T>::type val) {
  8. return test.t + val;
  9. }
  10. int main() {
  11. TestTemplate<long> test_template{1000};
  12. return add(test_template, 10);
  13. }

这里我们新添加了类型identity, 并利用typename identity<T>::type规避了模板的类型推断过程,从而让val的类型推断直接利用了test参数的类型推断结果,所以此时val的类型为long,模板类型推断也就不再出错了。

正是因为非推断语境在模板推断中会被使用,所以C++20提供了新的trait:

std::type_identitystd::type_identity_t来帮助我们解决上述的问题。它们的实现与功能与上面展示的identity一致,都是利用模板的非推断语境来规避类型推断不同导致的编译失败问题。

4.小结

C++的一些模板推断的问题常常让人抓狂,很多时候gcc给出的一长串报错很容易劝退萌新。本篇聊了聊笔者实际在开发中遇到的模板推断问题出发,一步步分析报错,希望大家对解决编译问题有耐心,并擅用搜索引擎,功力必不唐捐。(当然,更新的C++标准也给我们解决问题的武器库添砖加瓦,多多学习才是正道,日常一念:C++20好~~~

希望大家能够有所收获,笔者水平有限。成文之处难免有理解谬误之处,欢迎大家多多讨论,指教。

5.参考资料

CppReference

C++雾中风景17:模板的非推断语境与std::type_identity的更多相关文章

  1. C++语言基础(20)-模板的非类型参数

    一.在函数模板中使用非类型参数 #include <iostream> using namespace std; template<class T> void Swap(T & ...

  2. scala学习手记17 - 容器和类型推断

    关于scala的类型推断前面已经提到过多次.再来看一下下面这个例子: import java.util._ var list1: List[Int] = new ArrayList[Int] var ...

  3. 【模板】 非旋转treap

    模板:luogu P3369 [模板]普通平衡树 code: #include <cstdio> #include <cstdlib> const int MAX_N=1000 ...

  4. java基础17 模板模式

    1.模版模式 解决某类事物的步骤有些是固定的,有些会发生改变的,那么这个时候我们可以为这一类事物提供一个模版代码,从而提高效率. 2.模版模式的步骤 1.先写出解决该类事物的其中一种解决方案;     ...

  5. 【转】 史上最详尽的平衡树(splay)讲解与模板(非指针版spaly)

    ORZ原创Clove学姐: 变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结 ...

  6. 【模板】非旋转Treap

    Treap,也叫做树堆,是指有一个随机附加域满足堆的性质的二叉搜索树. 如果一棵二叉搜索树插入节点的顺序是随机的,那我们得到的二叉搜索树在大多数情况下是平衡的,期望高度是log(n). 但有些情况下我 ...

  7. c++11-17 模板核心知识(三)—— 非类型模板参数 Nontype Template Parameters

    类模板的非类型模板参数 函数模板的非类型模板参数 限制 使用auto推断非类型模板参数 模板参数不一定非得是类型,它们还可以是普通的数值.我们仍然使用前面文章的Stack的例子. 类模板的非类型模板参 ...

  8. 现代C++之理解模板类型推断(template type deduction)

    理解模板类型推断(template type deduction) 我们往往不能理解一个复杂的系统是如何运作的,但是却知道这个系统能够做什么.C++的模板类型推断便是如此,把参数传递到模板函数往往能让 ...

  9. C++17尝鲜:类模板中的模板参数自动推导

    模板参数自动推导 在C++17之前,类模板构造器的模板参数是不能像函数模板的模板参数那样被自动推导的,比如我们无法写 std::pair a{1, "a"s}; // C++17 ...

随机推荐

  1. docker轻量级监控-sysdig

    sysdig Sysdig = system(系统)+dig(挖掘).Sysdig 是一个开源系统发掘工具,用于系统级别的勘察和排障,可以把它看作一系列Linux系统工具的组合,主要包括: strac ...

  2. Svelte 码半功倍

    你未注意到的最重要的指标. 注意:原文发表于2019-04-20,随着框架不断演进,部分内容可能已不适用. 所有代码都有 BUG,你写的越多,BUG 越多,这很合情合理. 同时,写的越多,费时越多,留 ...

  3. 博客数据库要连接Elasticsearch,使用MySQL还是MongoDB更合理

    若进行博客等文本类数据的读写以及专业搜索引擎的连接的解决方案对比,可以肯定的下结论:MongoDB的解决方案中要远远好于MySQL的解决方案. 一.从开发工序角度 MySQL的文章读写方式 方式一:文 ...

  4. go中sync.Once源码解读

    sync.Once 前言 sync.Once的作用 实现原理 总结 sync.Once 前言 本次的代码是基于go version go1.13.15 darwin/amd64 sync.Once的作 ...

  5. js 检测当前浏览其类型

    需求:检测并打印当前使用的浏览器类型 <script type="text/javascript"> function getBrowser(){ const str ...

  6. 推荐模型DeepCrossing: 原理介绍与TensorFlow2.0实现

    DeepCrossing是在AutoRec之后,微软完整的将深度学习应用在推荐系统的模型.其应用场景是搜索推荐广告中,解决了特征工程,稀疏向量稠密化,多层神经网路的优化拟合等问题.所使用的特征在论文中 ...

  7. 题解 CF746D 【Green and Black Tea】

    # 题目分析这道题表面上看上去挺简单,其实仔细研究一下还是值得钻研的.我本人做这道题使用的任然是$ DFS01 $背包.不过呢,与往常背包不同的是,这次递归中需要加许多参数.就数据强度来看,栈问题不大 ...

  8. rman全备脚本

    cat rman_back.sh #!/bin/bash source /home/oracle/.bash_profile   rman log=/u01/backup/backupall_rman ...

  9. 七种join的书写规范

    在mysql中的两表进行连接时,总共有7种连接情况,具体可见下图 由图的从左到右的顺序 图1.左连接(left join):返回左表中的所有记录和右表中的连接字符字段相等的记录,若右表没有匹配值则补N ...

  10. Asp.Net Core 学习随笔

    1.依赖注入 configureServices 中 //单例 services.AddSingleton<i,c>(); //http请求内 services.AddScopend< ...