需要类型转换时请为模板定义非成员函数——条款46
? ? ? ? 條款24討論過為什么唯有non-member函數才有能力“在所有實參身上實施隱式類型轉換”,該條款并以Rational?class的operator*函數為例。我強烈建議你繼續看下去之前先讓自己熟稔那個例子,因為本條款首先以一個看似無害的改動擴充條款24的討論:本條款將Rational和operator*模板化了:
template<typename T>class Rational {public:Rational(const T& numberator = 0, const T& denominator = 1);const T numerator() const;const T denominator() const;... }; template<typename T> const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) { ... }? ? ? ? 像條款24一樣,我們希望支持混合式算數運算,所以我們希望以下代碼順利通過編譯。我們也預期它會,因為它正是條款24所列的同一份代碼,唯一不同的是Rational和operator*如今都成了templates:
Rational<int> oneHalf(1, 2); Rational<int> result = oneHalf * 2; // 錯誤!無法通過編譯? ? ? ? 上述失敗給我們的啟示是,模板化的Rational內的某些東西似乎和其non-template版本不同。事實的確如此。在條款24內,編譯器知道我們嘗試調用什么函數(就是接受兩個Rationals參數的那個operator*),但這里編譯器不知道我們想要調用哪個函數。取而代之的是,它們試圖想出什么函數被名為operator*的template具現化出來。它們知道它們應該可以具現化某個“名為operator*并接受兩個Rational<T>參數”的函數,但為完成這一具現化行動,必須先算出T是什么。問題是它們沒這個能耐。
? ? ? ? 為了推導T,它們看了看operator*調用動作中的實參類型。本例中那些類型分別是Rational<int>(oneHalf的類型)和int(2的類型)。每個參數分開考慮。
? ? ? ? 以oneHalf進行推導,過程并不困難。operator*的第一個參數被聲明為Rational<T>,而傳遞給operator*的第一實參(oneHalf)的類型是Rational<int>,所以T一定是int。其他參數的推導則沒有這么順利。operator*的第二參數被聲明為Rational<T>,但傳遞給operator*的第二實參(2)類型是int。編譯器如何根據這個推算出?你或許會期盼編譯器使用Rational<int>的non-explicit構造函數將2轉換為Rational<int>,進而將T推導為int,但它們不那么做,因為在template實參推導過程中從不將隱式類型轉換函數納入考慮。
? ? ? ? 只要利用一個事實,我們就可以緩和編譯器在template實參推導方面受到的挑戰:template?class內的friend聲明式可以指涉某個特定函數。那意味Rational<T>可以聲明operator*是它的一個friend函數。Class?templates并不依賴template實參推導(后者只施行于function?templates身上),所以編譯器總是能夠在class?Rational<T>具現化時得知T。因此,令Rational<T> class聲明適當的operator*為其friend函數,可簡化整個問題:
template<typename T>class Rational {public:...friendconst Rational operator*(const Rational& lhs, const Rational& rhs); }; template<typename T> const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) { ... }? ? ? ? 現在對operator的混合式調用可以通過編譯了,因為當對象?oneHalf被聲明為一個Rational<int>,?class?Rational<int>于是被具現化出來,而作為過程的一部分,friend函數operator*(接受Rational<int>參數)也就被自動聲明出來。后者身為一個函數而非函數模板,因此編譯器可在調用它時使用隱式轉換函數(例如Rational的non-explicit構造函數),而這便是混合式調用之所以成果的原因。
? ? ? ? 但是,此情境下的“成功”是個有趣的字眼,因為雖然這段代碼通過編譯,卻無法連接。稍后我馬上回來處理這個問題,首先我要談談在Rational內聲明operator*的語法。
? ? ? ? 在一個class?template內,template名稱可被用來作為“template和其參數”的簡略表達方式,所以在Rational<T>內我們可以只寫Rational而不必寫Rational<T>。本例中這只節省我們少打幾個字,但若出現許多參數,或參數名稱很長,這可以節省我們的時間,也可以讓代碼比較干凈。我談這個是因為,本例中的operator*被聲明為接受并返回Rationals(而非Rational<T>s)。如果它被聲明如下,一樣有效:
template<typename T>class Rational {public:...friendconst Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs);... };? ? ? ? 然而使用簡略表達式比較輕松也比較普遍。
? ? ? ? 現在回頭想想我們的問題。混合式代碼通過了編譯,因為編譯器知道我們要調用哪個函數,但哪個函數只被聲明與Rational內,并沒有被定義出來。我們意圖令此class外部的operator*?template提供定義式,但是行不通——如果我們自己聲明了一個函數,就有責任定義那個函數。既然我們沒有提供定義式,連接器當然找不到它!
? ? ? ? 或許最簡單的可行辦法就是將operator*函數本體合并至其聲明式內:
template<typename T>class Rational {public:...friendconst Rational operator*(const Rational& lhs, const Rational& rhs){return Rational(lhs.numberator() * rhis.numberator(), lhs.denominator() * rhs.denominator());} };? ? ? ? 這便如同我們所期望地正常運作了起來:對operator*的混合式調用現在可以編譯并執行。
請記住
- 當我們編寫一個class?template,而它所提供之“與此template相關的”函數支持“所有參數之隱式類型轉換”時,請將那些函數定義為“class?template內部的friend函數”。
總結
以上是生活随笔為你收集整理的需要类型转换时请为模板定义非成员函数——条款46的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入理解anchor
- 下一篇: 我的世界服务器怎么弄领地语言,我的世界领