前言
PCL中的surface/include/pcl/surface/mls.h
裡宣告的pcl::MovingLeastSquares
類別有這麼一段代碼:
using KdTree = pcl::search::Search<PointInT>;
//typename specifies that it is the name of a type
using KdTreePtr = typename KdTree::Ptr;
// 模板中的參數已經給定,是個具體的類型,所以不用加typename告知編譯器它是一個類別
using NormalCloud = pcl::PointCloud<pcl::Normal>;
using NormalCloudPtr = NormalCloud::Ptr;
using PointCloudOut = pcl::PointCloud<PointOutT>;
using PointCloudOutPtr = typename PointCloudOut::Ptr;
using PointCloudOutConstPtr = typename PointCloudOut::ConstPtr;
using PointCloudIn = pcl::PointCloud<PointInT>;
using PointCloudInPtr = typename PointCloudIn::Ptr;
using PointCloudInConstPtr = typename PointCloudIn::ConstPtr;
using SearchMethod = std::function<int (pcl::index_t, double, pcl::Indices &, std::vector<float> &)>;
觀察KdTree
和KdTreePtr
,可以發現一個沒加,另一個則有加typename
。他們之間的區別詳見下文。
KdTreePtr
KdTree::Ptr
簡化前為pcl::search::Search<PointInT>::Ptr
,因為它具體是什麼要依賴於PointInT
,在編譯時並不知道它是一個類別,所以在這裡加上typename
讓編譯器知道。
參考C++ Cast Template,如果我們要調用cast
對pcl::search::Search<PointInT>::Ptr
做型別轉換,編譯器在不知道pcl::search::Search<PointInT>::Ptr
是一個類型的情況下,可能會把cast
當成它的成員變量,因而導致編譯錯誤。所以這裡加上typename
讓編譯器知道它是一個類別後,就能成功編譯了。
KdTree
那麼為什麼KdTree
不用加typename
呢?參考Where and why do I have to put the “template” and “typename” keywords?:
There are many names for which typename is not necessary, because the compiler can, with the applicable name lookup in the template definition, figure out how to parse a construct itself - for example with T *f;, when T is a type template parameter. But for t::x * f; to be a declaration, it must be written as typename t::x *f;.
我們可以用模板參數T
直接去定義一個變量,但是如果想要用T::x
定義一個變量卻是行不通的(編譯器會把x
當作T
類型的成員變量),必須要在T::x
之前加上typename
,讓編譯器知道它是一個類型才行。
The syntax allows typename only before qualified names - it is therefore taken as granted that unqualified names are always known to refer to types if they do so.
typename
關鍵字只能加在qualified names(可以想成有帶::的類別名稱)之前,unqualified names總是會被當成類別名稱,所以不需要加typename
關鍵字。
來看一下qualified name的定義,參考Qualified name lookup:
A qualified name is a name that appears on the right hand side of the scope resolution operator :: (see also qualified identifiers). A qualified name may refer to a
class member (including static and non-static functions, types, templates, etc)
namespace member (including another namespace)
enumerator
它將qualified name的定義說得很明白,也就是::
運算符後面的名稱。qualified name可以是一個類別的成員,命名空間的成員,或是enum。
在這邊的例子中,將模板參數PointT
代入pcl::search::Search<PointInT>
後,編譯器便可以很容易地知道它是一個類型(對照上面的T* f
),所以不需要加typename
。
如果我們想用pcl::search::Search<PointInT>::Ptr
定義一個變數,因為它是一個qualified name(對照上面的T::x* f
),所以必須在它之前加上typename
。
範例程序
以下程序展示typename
的使用時機:
#include <iostream>
#include <memory>
template<typename T>
class Animal{
public:
using Ptr = std::shared_ptr< Animal< T > >;
};
template<typename T>
void f(){
Animal<T> a;
//error: need ‘typename’ before ‘Animal<T>::Ptr’ because ‘Animal<T>’ is a dependent scope
//error: expected ‘;’ before ‘ap’
//Animal<T>::Ptr ap;
typename Animal<T>::Ptr ap;
}
int main(){
Animal<int> a;
Animal<int>::Ptr ap;
return 0;
}
首先定義一個模板類別Animal
,裡面有個成員Ptr
。
在main
函數中,因為我們給定了Animal
的模板參數,所以編譯器知道他們兩個都代表一種類型,可以成功編譯。
在f
函數中,我們可以用Animal<T>
定義一個變數a
,但是卻無法用Animal<T>::Ptr
去定義變數ap
,必須在之前加上typename
才行。這是因為在T
不確定的情況下,編譯器把Ptr
當成Animal<T>
類別的成員變數而不把它視為一個類型,因此無法用它去定義其他變數。
以上程序放在typename_keyword.cpp。