文章

在vs2017进行泛型模板编程的一些笔记

前言

我周末用c++,写数据结构的作业,出于装逼炫技(这两个词好像没差)的目的,我用oop的思想,和**现代c++**的语法来写作业。

其中我很自然的使用了泛型加上一些一些工程技巧。

而很遗憾的的是,当中出现了非常多的一些问题,一些是由于我对于c的学习还不是很熟练,另一些则是因为编译器对于modern c支持得不好,或者vs对它支持的不好。

模板类中的模板类中的子类

读上去好像有点绕口,我们举个栗子

template<class T>
class O
{
private:
    vector<T> ts;
public:
    O(vector<T> sourse)
        :ts(sourse)
    {
        vector<T>::itereator it = ts.begin();
    }
};

看上去没啥问题,可是一旦我们编译,就会

错误

只是为什么呢?

因为vector<T>::itereator这段语句内,因为vector<T>是不确定的,所以我们的编译器无法确认所谓的iteratoe(注:图中的代码,单词打错了,但是不影响结果,望别介意)是vector<T>的子类还是静态变量

注:子类这个说法是不准确的,详见stl源码

二义性是计算机系统难以接受的,所以报错。

至于为什么IDE不报错,又是另一个故事了

为了消除二义性

template<class T>
class O
{
private:
    vector<T> ts;
public:
    O(vector<T> sourse)
        :ts(sourse)
    {
        typename vector<T>::iterator;
    }

};

像我这样加上typename就可以消除二义性,声明后面的iterator是一个类型,而非一个变量。

没有提示,没有报错

像是我们上文的代码,如果你的IDE有自动补全的话,你就会发现ts.这样的代码会触发自动补全而vector<T>这样的代码就不行。

![有](https://i.loli.net/2018/12/01/5c024a663c6b6.png)
![没有](https://i.loli.net/2018/12/01/5c024a4618fb7.png)

这可能有比较深的原因,但是挺坑的,就记一下吧

没有动态报错情况详见代码

1.png

对于模板函数或者模板类内部的代码,不会进行动态检验,因此使用vs写的时候,我们的体验就回归了vc++(==)。

至于原因,我咨询了大佬,大佬也不知道,那就凉凉喽

error type的原因

出现这种情况有两种可能

  • 参数类型写错了,这是绝大多数的情况,因为vs的类型推导十分zz,再加上没有动态检验,就会出现类型推到错误,也就是说函数的第一个参数写错了,后面的参数以及函数体里的变量都会显示error-type
  • 类的顺序错了,不说了,太zz了

分离编译

如果你恰好遇到了编译失败,原因是符号问题,那你可能是吃了分离编译的亏,因为,现在的编译器没有一个支持泛型的分离编译的。因为编译是对单个文件进行的,之后再进行链接。

error LNK2019: 无法解析的外部符号 "void __cdecl func< int>(int const &)" (??$func@H@@YAXABH@Z)

我们来复习一下模板的实现

  1. 程序员写出模板(例如模板函数)
  2. 程序员在别的地方确立对于模板的调用
  3. 编译器将模板的实例化调用符号化为符号
  4. 根据符号来生成对应符号化的模板函数

也就是说当编译过程一过,编译器将不会再去实例化函数

参考一位大佬的回答

原因出现在分离编译模式上。在分离编译模式下,func.cpp会生成一个目标文件为func.obj,由于在func.cpp文件中,并没有发生函数模板调用,所以不会将函数模板func< T>实例化为模板函数func< int>,也就是说,在func.obj中无法找到关于模板函数func< int>的实现代码。在源文件main.cpp中,虽然函数模板被调用,但由于没有模板代码,也不能将其实例化。也就是说,在main.obj中也找不到模板函数func< int>的实现代码。这样,在连接的时候就会出现func< int>没有定义的错误。

所以要把函数的定义和声明放在同一个文件

在同一个文件里进行类似调用也行,但是不推荐

和普通函数不同的是,这里不会报函数重定义的错,这是对于模板函数的特殊照顾

泛型的尖括号不补全

这问题请找罪魁祸首微软

静态函数成员的一些问题

当我们定义一些类的静态方法时,我们会很震惊的发现,vs把静态方法的声明和下面的定义视为两个符号,而定义的函数体内,会失去绝大多数高亮补全,或者报错。而且内部的符号在vs中全部是未定义状态。而编译器最后解析的时候都是一视同仁的。

解决方法

可以考虑将定义和声明放在一起~~,但是并没有解决实际问题23333~~

至于真正的解决方法,只有等vs更新了。

模板类内双重模板与auto的推导错误

假定有

template <typename T>
void ShowProcess(vector<vector<T>> elements, string tip)
{
    cout << tip << endl;
    for (auto line : elements)
    {
        for (auto element : line)
        {
            cout << setw(4) << element;
        }
        cout << endl;
    }
    cout << endl;
}

按照正常情况,这段代码一点问题都没有。

实际上这里会出现推导错误

line会被推导为T类型,然后雪崩

应该改为

template <typename T>
void ShowProcess(vector<vector<T>> elements, string tip)
{
    cout << tip << endl;
    for (vector<T> line : elements)
    {
        for (auto element : line)
        {
            cout << setw(4) << element;
        }
        cout << endl;
    }
    cout << endl;
}
License:  CC BY 4.0