2011-03-05 103 views
11

我最近試圖創建一個is_class類,並且需要一種編譯器區分定義轉換運算符的枚舉類型和類類型的方法。看到類,結構和聯合是如何與指向成員函數兼容的唯一類型,我決定讓編譯器確定用於實例化is_class模板的類型是否與指向成員函數的代碼兼容。在遇到幾個問題後,我決定在與指向成員的指針結合使用時測試枚舉的行爲,並得到一些古怪的結果。下面段示出了第一怪癖:枚舉和指向成員

enum ENUM {}; 
void Test(void (ENUM::*pmem) (void)) 
{ 
    /* ... */ 
} 
Test(NULL); 

當使用Microsoft Visual C++ 2010編譯,函數定義的指針到構件部分:(ENUM::*pmem)

被高亮顯示爲紅色,鼠標懸停聲明揭示錯誤:

Error: "ENUM" is not a class type

然而,編譯器解析這個段沒有遇到任何錯誤,分配給pmemNULL。我很感興趣的是,編譯器可以讓枚舉類型不是類,結構或聯合,因此不能擁有自己的方法。

template<class _Ty> 
void Test_Template(void (_Ty::*pmem) (void)) 
{ 
    /* ... */ 
} 

當然,爲了使用這一功能,就必須顯式限定:創建模板功能時,採用指針到成員的說法,其類型而異

的第二興趣點出現了:

Test_Template<ENUM>(NULL); 

這個調用然而,會產生錯誤,指出:

invalid explicit template argument(s) for 'void Test(void (__thiscall _Ty::*)(void))'

我通過創建一個附加函數模板解決了這個問題,該模板的原型將匹配任何調用失敗的匹配原模板函數的原型(涉及使用省略號)。

問題:

  1. 爲什麼是指針到成員兼容枚舉?

  2. 爲什麼在調用非模板Test函數時存在完全匹配,而編譯器爲模板生成了錯誤Test_Template顯式限定?

+0

它看起來像'T ENUM :: * D;'是一個語法上有效的聲明。我找不到任何明確表示它在語義上不合格的東西。但是,如果格式良好,那麼它將是一個聲明,其中第8節沒有指定'D'的類型,這很奇怪。 – aschepler 2011-03-05 20:53:47

+3

感謝這個問題,很高興看到超出基本水平的問題偶爾會發生,並且這個問題讓我的頭腦開始攪動:) – 2011-03-05 21:03:57

+0

g ++ 4.4.5 -std = C++ 0x同樣接受'Test',但是在一個試圖調用'Test_Template (static_cast (0))'給出錯誤「沒有匹配函數調用Test_Template(void(ENUM :: *)())」「 – aschepler 2011-03-05 22:34:12

回答

2

關於你的第一個問題,看起來編譯器確實報告枚舉不能具有成員函數,因爲編譯器報告函數聲明有錯誤。這可能是通過內部嘗試糾正錯誤聲明來讓調用成功,在這種情況下,這意味着您注意到您試圖聲明類似指針的內容並允許調用。沒有要求編譯器給你提供該行的錯誤;由於程序是由I'll形成的,只要編譯器拒絕帶有診斷的程序,它就不需要在任何地方給出錯誤。

至於你的第二個問題,有第二個模板導致錯誤消失的原因是"substitution failure is not an error" (SFINAE) principle。當編譯器使用某些類型參數實例化函數模板時,如果它發現特定的函數實例化無效(例如,試圖獲取指向枚舉成員的指針),它不會報告錯誤。相反,它只是從考慮中刪除該模板。但是,如果使用給定的參數實例化時,所寫的模板都不是有效的,那麼編譯器會發出錯誤信息,因爲它無法找到您正在嘗試執行的操作。在第一種情況下,如果只有一個模板,則會發生錯誤,因爲SFINAE從考慮中消除了唯一的模板候選項,導致模板瞬時沒有匹配的模板。在第二種情況下,在實例化模板後,您的「全部捕捉」模板仍然有效,因此,當排除指向成員的指針的模板時,仍然會有一個合法的模板供您參考。因此,代碼非常好。