一、背景
使用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关键字的情况作了某些默认的推导。
所以为了代码的通用性,还是使用后面这种写法吧,这样在使用不同编译器时就不需要针对特定的编译器进行代码修改了。