Template Resolver
那首先,就是更好的模板支持,这也是我最开始想要 clangd 支持的特性。具体来说目前在处理模板上有什么问题呢?
以代码补全为例,考虑如下的代码,^
代表光标位置:
template <typename T>
void foo(std::vector<T> vec) {
vec.^
}
在 C++ 中,如果一个类型依赖于模板参数,那么在模板实例化之前,我们并不能对它做出任何准确的假设。例如这里的vector
即可能是主模板也可能是vector<bool>
的偏特化,选哪一个呢?对于代码编译来说,准确性永远是最重要的,不能使用任何可能导致错误的结果。但是对于语言服务器来说,提供更多可能的结果往往比什么都不提供更好,我们可以假设用户在更多时候使用主模板而不是偏特化,从而基于主模板来提供代码补全的结果。目前 clangd 也确实是这么做的,在上述情况下它会根据vector
的主模板为你提供代码补全。
再考虑一个更加复杂的例子:
template <typename T>
void foo(std::vector<std::vector<T>> vec2) {
vec2[0].^
}
从用户的角度来说,这里也应该提供补全,毕竟vec2[0]
的类型不也是vector<T>
吗?和前面一个例子一样。但是 clangd 在这里却不会为你提供任何补全,问题出在哪里?根据 C++ 标准,std::vector<T>
的operator[]
返回的类型是std::vector<T>::reference
,这其实是一个 dependent name,它的结果似乎相当直接,就是T&
。但是 libstdc++ 中它的定义却嵌套了十几层模板,似乎是为了兼容旧标准?那为什么 clangd 不能处理这种情况呢?
- 它基于主模板假设,不考虑偏特化可能会使查找无法进行下去
- 它只进行名称查找而不进行模板实例化,就算找到了最后的结果,也没法把它和最初的模板参数映射起来
- 不考虑默认模板参数,无法处理由默认模板参数导致的依赖名
尽管我们可以对标准库的类型开洞来提供相关的支持,但是我希望用户的代码能和标准库的代码有相同的地位,那么我们就需要一种通用的算法来处理依赖类型。为了解决这个问题,我编写了一个伪实例化器(pseudo instantiator)。它能在没有具体类型的前提下对依赖类型进行实例化,从而达到化简的目的。比如上面这个例子里面的std::vector<std::vector<T>>::reference
就能被化简为std::vector<T>&
,进一步就能为用户提供代码补全选项。