2013-08-20 65 views
1

以下C++代碼不MS的Visual Studio 2010編譯:方法指針模板不編譯

class Foo 
{ 
public: 
    /// Provides the signature of the methods that can be given to addValueSetListener 
    template <typename TT> 
    struct ChangeHandler 
    { 
     typedef void (TT::* OnSetValueMethod)(); 
    }; 

    template <typename TT> 
    void bar_ok(TT*, void (TT::*)(), bool = false) {} 
    template <typename TT> 
    void bar_ok(const char*, TT*, void (TT::*)()) {} 

    template <typename TT> 
    void bar_fails(TT*, typename ChangeHandler<TT>::OnSetValueMethod, bool = false) {} 
    template <typename TT> 
    void bar_fails(const char*, TT*, typename ChangeHandler<TT>::OnSetValueMethod) {} 

    void testBar() {} 
}; 

int main() 
{ 
    Foo foo; 
    foo.bar_ok ("allo",& foo, & Foo::testBar); // compiles 
    foo.bar_fails("allo",& foo, & Foo::testBar); // compile ERROR 
} 

編譯器誤差爲'TT': must be a class or namespace when followed by '::',用於ERROR線。

失敗的行和唯一不同的行之間的唯一區別是bar_fails通過「模板化typedef」聲明「方法指針類型」參數void (TT::*)(),而bar_ok直接聲明它。

請注意,如果沒有const char*的超載,模板化的typedef可以正常工作。在const char *過載可用的情況下,編譯器錯誤地選擇了bar_fails的重載,但它正確地選擇了bar_ok的TT = Foo重載。當typedef適用於像TT *或float *這樣的「簡單」數據時,不會出現此問題。

+0

您標記爲'// compiles'的行沒有辦法。所有這些成員都是「私人」的開始。你能告訴我們真實的代碼嗎? –

+0

'main'也返回'int' ...(看起來是拼寫錯誤) –

+0

@PierreFourgeaud對不起,我修復了這個問題 – Schollii

回答

2

原因是在bar_ok的情況下,SFINAE可以適用,因爲錯誤的構造const char::*出現在模板參數替換的直接上下文中。在bar_fail的情況下,它被刪除了一步(隱藏在「template typedef」中),這意味着SFINAE不再適用,編譯器必須處理const char::*的句法廢話,從而停止並報告錯誤。

換句話說,並不是編譯器在bar_fail中選擇錯誤的超載。它必須在兩種情況下檢查兩種超載情況,但第一種情況是允許SFINAE忽略錯誤的情況,而第二種情況是「太遲了」。

+0

我沒有注意到「直接上下文」的微妙之處,但經過基於這個答案的進一步測試,這確實是問題。解決方法在下一個答案中,因爲我無法將其納入評論。我將離開Angew的回答作爲接受的答案,因爲如果沒有它,我不知道去哪裏尋找。謝謝Angew! – Schollii

1

Angew提供了一個簡單的解釋,爲什麼我的OP中的代碼不起作用。那麼是否有解決方法?原來它很簡單,但解釋不適合評論,所以在這裏。

如Angew指出,SFINAE僅適用於直接取代水平,所以這個編譯:

template <typename TT> void testFunc(TT) {} 
template <typename TT> void testFunc(typename TT::Foo) {} 
int main() 
{ 
    testFunc<int>(3); 
} 

事實上編譯器知道到下降的testFunc第二過載:一個整數不具有嵌套Foo類型。如果SFINAE在C++中完全不可用,則會停止編譯。現在

如果你改變了上面的實現略有使用特性,像類,下面的完全等價的代碼不再編譯:

template <typename TT> 
struct Helper 
{ 
    typedef typename TT::Foo MyFoo; 
}; 
template <typename TT> void testFunc(TT) {} 
template <typename TT> void testFunc(typename Helper<TT>::MyFoo) {} 

int main() 
{ 
    testFunc<int>(3); 
} 

因爲編譯器是「內部」的Helper<int>類時,它是解決MyFoo typedef;它在int::Foo,直接SFINAE不適用,所以它放棄編譯。

你可能注意到了,在我的OP,我並沒有明確在上述指定模板參數,而我做的:這是因爲編譯器知道參數是一個int這樣,那需要int作爲參數的所有testFunc相匹配,它不會嘗試所有的testFunc<int>。在我的OP中,我不需要明確指定模板參數來獲取錯誤,因爲第一個函數參數爲我做了這個。讓我們廢除的方法指針,因爲它密佈的問題,我原來的問題是由以下簡單的代碼顯示在這裏我沒有明確指定模板參數:

struct Foo 
{ 
    template <typename TT> struct Helper 
    { 
     typedef typename TT::Foo MyFoo; 
    }; 

    template <typename TT> void bar(TT*, typename Helper<TT>::MyFoo) {} 
    template <typename TT> void bar(const char*, TT*) {} 
}; 


int main() 
{ 
    Foo foo; 
    foo.bar("allo", & foo); // ok 
} 

編譯器看到的第一個函數調用參數foo.bar作爲const char*,所以它知道它必須同時考慮bar(const char*, Foo*)bar(const char*, Helper<const char>::MyFoo)。然後在Helper<const char>裏面試圖解決const char::Foo並暫停編譯(再次指出SFINAE不適用)。

一種解決方法是刪除Helper,堅持直接嵌套類型:

struct Foo 
{ 
    template <typename TT> void bar(TT*, typename TT::Foo) {} 
    template <typename TT> void bar(const char*, TT*) {} 
}; 

然而,這是不理想的,因爲我在我實際的代碼我bar()期待一個方法指針,我想在聲明中明確地說明這一點(儘管我現在想知道如果模板化typedef有幫助或阻礙,但那是另一回事)。

第二種解決方案是使用SFINAE在bar級別編譯器阻止。原來,這是很容易做到:定義Helper一個專門爲const char是缺少的部分:現在,當編譯器相匹配TTconst char

struct Foo 
{ 
    template <typename TT> struct Helper 
    { 
     typedef typename TT::Foo MyFoo; 
    }; 
    template <> struct Helper<const char> 
    { 
    }; 

    template <typename TT> void bar(TT*, typename Helper<TT>::MyFoo) {} 
    template <typename TT> void bar(const char*, TT*) {} 
}; 

,它有兩個重載需要考慮:

void bar(const char*, Helper<const char>::MyFoo) 
void bar(const char*, Foo*) 

Helper<const char>::MyFoo在專業化中不存在,所以可以使用SFINAE:編譯器不必去「內部」Helper<T>

這3條專業化線路足以解決這個問題。

+0

很好解釋,+1。 – Angew