一、背景

使用typedef或者using定义类型别名是非常常见的手段,在c++里面,有时为了封装性,模块性等原因还会在某一个namespace或者class内部定义类型别名。

最近在写c++代码的时候,有实现一个模板类,说实话,虽然用c++用了好多年了,但还真没花多少时间去研究模板,因为我始终觉得,做项目,开发软件,不是为了炫技,我也不认为会玩儿模板就是牛人大神了,最主要的是把握好三个“用”就好了,这三个用分别是:实用,适用,够用。

言归正传,这次实用模板类,也会用到在模板类里面实用typedef定义类型别名,当然,其实这也没什么问题,只要在模板类内部实现每个模板类的成员函数,这一切都不是问题,但是我想让模板类声明的干净纯粹一些,换句话说,就是将模板类的成员函数全部都放到外面去实现,而不是直接的模板类内部实现,模板类只是声明每个成员函数。

问题就来了,欲知详情,请接着往下看。

二、解决方案

假定这里需要实现这样一个模板myFoo, 这个模板有一个成员函数size,这个成员函数会返回一个表示size的值,其声明大致如下:

template<typename T>
class myFoo
{
public:
using size_type = std::size_t; public:
size_type size() const;
private:
size_type mSize = 5;
};

  这里如果只是在模板类里面实现这个size成员函数的话,就不会有任何问题,形如:

template<typename T>
class myFoo
{
public:
using size_type = std::size_t; public:
size_type size() const
{
return mSize;
}
private:
size_type mSize = 5;
};

  问题就是,我需要在模板类外面实现这个size成员函数,如果按照普通的c++类来实现,如下所示:

myFoo::size_type myFoo::size() const
{
return mSize;
}

  当然,如果你这样写的话,编译器是不会让你过去的,模板类的成员函数在类外面实现自有它的规则,形如:

template<typename T>
myFoo<T>::size_type myFoo<T>::size() const
{
return mSize;
}

  

当然,如果你走到这一步了,说明你对模板类外实现成员函数的规则算是基本了解了,但这种写法仍然不对,也会被编译器无情的拒绝。

因为,对于模板类,myFoo::size_type 这样的写法,会被认为是引用一个名为size_type的成员变量,而这里的size_type只是size_t的别名而已,是个类型名,而不是成员变量。

答案最终揭晓,正确的写法是,保证对size_type的引用是类型名的引用,其实解决方法并不止一种:

  • 其一:使用typename关键字,具体代码如下:
template<typename T>
typename myFoo<T>::size_type myFoo<T>::size() const
{
return mSize;
}
  • 其二:使用auto和decltype关键字,具体代码如下(包含模板类的声明部分):
template<typename T>
class myFoo
{
public:
using size_type = std::size_t; public:
auto size() const -> decltype(myFoo<T>::mSize);
private:
size_type mSize = 5;
}; /** size成员函数实现 */
template<typename T>
auto myFoo<T>::size() const -> decltype(myFoo<T>::mSize)
{
return mSize;
}

  

两种写法都可,选哪一种就看个人习惯和爱好了(其实:在visual studio2017里面只需要auto关键字就可以了,不需要decltype关键之,后面会说到)。

三、模板函数的返回值类型推导

这里顺便说一下,模板函数的返回值类型推导, 举一个简单的例子,实现一个将两个变量相加的模板函数,原型大致为:

template<typename T1, typename T2>
xxx myAdd(T1 a, T2 b);

  

之所以这里的返回值,是一个“xxx”, 是因为无法确定这个返回值类型会是什么,如果T1和T2都是相同类型如int,那么还好办,“xxx”取T1或者T2都可以,但如果T1和T2类型不同,比如一个是int,一个double,理论上,应该返回double类型, 但是这个模板函数并不知道T1是double,还是T2是double。

这里你可以要说,这简单啊,直接用auto关键字就好了,这里需要说明一下,我试验的结果是,使用visual studio2017的话,用auto关键字作为这个模板函数的返回值是可以的,其形式如下:

template<typename T1, typename T2>
auto myAdd(T1 a, T2 b)
{
return a + b;
}

  但是如果使用gcc的话,这样写就不行,会报如下的错误:

‘myAdd’ function uses ‘auto’ type specifier without trailing return type

  也就是说,需要说明模板函数返回值的推导方式,根据c++11标准,这种情况需要在模板函数后面用decltype关键字添加返回值类型的推导,代码如下:

template<typename T1, typename T2>
auto myAdd(T1 a, T2 b) -> decltype(a + b)
{
return a + b;
}

  

这种写法在visual studio2017和gcc中都能编译通过,这里不太清楚,是否visual studio 2017对只使用auto关键字的情况作了某些默认的推导。

所以为了代码的通用性,还是使用后面这种写法吧,这样在使用不同编译器时就不需要针对特定的编译器进行代码修改了。

[转]引用模板类中定义的类型(用typedef或using)以及auto、decltype、typename的使用的更多相关文章

  1. 基类中定义的虚函数在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型及参数的先后顺序,都必须与基类中的原型完全相同 but------> 可以返回派生类对象的引用或指针

      您查询的关键词是:c++primer习题15.25 以下是该网页在北京时间 2016年07月15日 02:57:08 的快照: 如果打开速度慢,可以尝试快速版:如果想更新或删除快照,可以投诉快照. ...

  2. C++模板类中使用静态成员变量(例如Singleton模式)

    一个最简单Singleton的例子: ///////// Test.h /////////template <class _T>class CTest{private:_T n;stati ...

  3. gcc的bug? c++模板类中友元函数的訪问权限问题

    原文地址:http://stackoverflow.com/q/23171337/3309790 在c++中,模板类中能够直接定义一个友元函数.该函数拥有訪问该模板类非public成员的权限. 比方: ...

  4. C++模板类中友元函数的写法

    首先,已声明好的类Triangle file://Triangle.h template<class T> class Triangle{ public: Triangle(T width ...

  5. MVC中子页面如何引用模板页中的jquery脚本

    MVC中子页面如何引用模板页中的jquery脚本 最近在学习mvc,遇到了一个问题:在html页面中写js代码,都是引用mvc5自带的jquery脚本,虽然一拖(将指定的jquery脚本如 jquer ...

  6. MFC 如何在一个类中使用在其他类中定义的变量或函数

    [声明:本文的知识点来源于网络,参考网址:https://blog.csdn.net/bill_ming/article/details/7407848] [以下三种方法亲测有效,可以根据具体情况来选 ...

  7. ES6 class类中定义私有变量

    ES6 class类中定义私有变量 class类的不足 看起来, es6 中 class 的出现拉近了 JS 和传统 OOP 语言的距离.但是,它仅仅是一个语法糖罢了,不能实现传统 OOP 语言一样的 ...

  8. 泛型方法或泛型类中的方法是内部调用、PInvoke 或是在 COM 导入类中定义的。

    泛型基类中引用Api函数定义时static extern,在子类中会提示: 未处理TypeLoadException 泛型方法或泛型类中的方法是内部调用.PInvoke 或是在 COM 导入类中定义的 ...

  9. java类中定义接口

    今天看到一个java类中定义了接口,写个备忘录,记录一下 package com.gxf.test; public class Test_interface { public interface sh ...

随机推荐

  1. Scratch编程:游泳的火柴人(四)

    “ 上节课的内容全部掌握了吗?反复练习了没有,编程最好的学习方法就是练习.练习.再练习.一定要记得多动手.多动脑筋哦~~” 01 — 游戏介绍 这是一款简单的小游戏,实现了一个手绘的火柴人在水里游泳. ...

  2. 使用应用编排服务一键式部署,持续集成利器--jenkins

    这篇文章主要是来聊一聊jenkins,可说道jenkins,我没有办法不把它与持续集成(Continuous integration,简称CI)联系到一起,所以我先来谈谈什么是持续集成以及为什么需要持 ...

  3. 音视频入门-09-RGB&YUV互转-使用开源库

    * 音视频入门文章目录 * 介绍开源库 使用第三方开源库来简化开发,屏蔽一些底层的复杂度,节省大量编写代码的时间. libyuv: Google 开源的实现各种 YUV 与 RGB 之间相互转换.旋转 ...

  4. Go 操作 Mysql(三)

    什么是事务? 事务是数据库非常重要的部分,它具有四大特性(原子性.一致性.隔离性.持久性) 以下内容出自<高性能MySQL>第三版,了解事务的ACID及四种隔离级有助于我们更好的理解事务运 ...

  5. Java中的反射机制和动态代理

    一.反射概述 反射机制指的是Java在运行时候有一种自观的能力,能够了解自身的情况为下一步做准备,其想表达的意思就是:在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法:对于任意一个对象 ...

  6. VBA错误处理(十八)

    在(VBScript/VBA)编程中有三种类型的错误: 语法错误 运行时错误 逻辑错误 语法错误 语法错误(也称为解析错误)发生在VBScript的解释时间. 例如,下面一行导致语法错误,因为它缺少一 ...

  7. 【转】使用Scanner输入字符串时next()和nextLine()区别

    在实现字符窗口的输入时,很多人更喜欢选择使用扫描器Scanner,它操作起来比较简单.在编程的过程中,我发现用Scanner实现字符串的输入有两种方法,一种是next(),一种nextLine(),但 ...

  8. jQuery遍历(2)

    上期我们讲了遍历的祖先和后代的问题,现在我们讲讲遍历同胞 同胞拥有相同的父元素. 通过 jQuery,您能够在 DOM 树中遍历元素的同胞元素. jQuery siblings() 方法 siblin ...

  9. 最新版Navicate破解激活

    2019年5月5日激活成功 版本12.1.18 Navicat12.1下载地址 http://www.navicat.com.cn/download/navicat-premium有32位和64位,大 ...

  10. git命令——git add

    如何理解git add git add命令本身并不复杂,字面意义上理解是“将一个文件添加到项目中“.但是这种理解有缺陷,有时候可能会出现某个文件同时存在暂存区域 和 非暂存区域(staged and ...