2010-10-04 93 views
3

我有關於成員指針的問題。下面的代碼無法同時使用的Oracle Solaris Studio 12.2的的CC編譯和Cygwin GCC 4.3.4,但與微軟的Visual C++工程2010:指向基類成員的指針的類型

struct A { 
    int x; 
}; 

struct B : public A { 
}; 

template<typename T> class Bar { 
public: 
    template<typename M> void foo(M T::*p); 
}; 

int main(int, char *[]) { 
    Bar<B> bbar; 
    bbar.foo(&B::x); 
    return 0; 
} 

在旁邊的最後一行上述兩種編譯器無法找到匹配Bar<B>::foo(int A::*)。我寫了一個簡單的測試,以確認該表達&B::x的類型實際上int A::*是:

// ... 

static void foo(int A::*p) { 
    std::cout << "A" << std::endl; 
} 

static void foo(int B::*p) { 
    std::cout << "B" << std::endl; 
} 

int main(int, char *[]) { 
    foo(&B::x); // prints "A", even on MS VC++ 2010 
    return 0; 
} 

以下解決方法可與GCC(不與Oracle CC尚未測試過),但無法與VC++由於歧義:

template<typename T> class Bar { 
public: 
    template<typename M> void foo(M T::*p); 
    template<typename M, typename _T_base> inline void foo(M _T_base::*p) { 
     foo(static_cast<M T::*>(p)); 
    } 
}; 

我的問題: 哪種行爲正確?顯然,VC++會從int A::*int B::*隱式轉換以滿足對成員函數模板的調用,其他兩個編譯器是否不應該這樣做?

+1

也對答案感興趣,因爲我自己也遇到了類似的問題。我的理解是,由於多重繼承,'&B :: x'被默默地轉換爲'&A :: x'的錯誤是錯誤的:'B'的實例可能包含幾個'A'的實例,因此'&B :: x (可能)不明確。 – Dummy00001 2010-10-04 22:33:16

+0

從我刪除的答案:'static_cast < int B::* >(&B :: x)'也適用。 – Potatoswatter 2010-10-04 23:18:29

+0

@ Dummy00001:我懷疑,如果存在多重繼承,編譯器將像所有其他基類成員分辨率一樣運行,並輸出錯誤,指出名稱不明確。如果編譯器能夠解析名稱(應該在這裏),那麼它應該毫不費力地解決它。 – 2010-10-04 23:19:17

回答

5

允許從int A::*int B::*的轉換,這不是問題所在。問題出現在模板參數推導中,正如您可以看到的,如果您嘗試提供以下程序,該程序爲B::foo提供了模板參數<int>並進行了編譯,並且產生與之前做的B::foo相同的錯誤的非成員函數foo2

struct A { 
    int x; 
}; 

struct B : public A { 
}; 

template <typename T> class Bar { 
public: 
    template<typename M> void foo(M T::*p); 
}; 

template<typename M> void foo2(M B::*p); 

int main(int, char*[]) { 
    Bar<B> bbar; 
    bbar.foo<int>(&B::x); 
    foo2(&B::x); // error, but foo2<int>(&B::x) would work. 
    return 0; 
} 

我認爲這種情況是不包括在其中編譯器應該推斷出模板參數<int>自身的情況。 14.8.2.1p3:

通常,推導過程試圖找到將推導出的A與A相同(在如上所述對A型進行變換之後)的模板參數值。但是,有三種情況,允許的差:

  • 如果原始P是引用類型,推導A(即,由參考提到的類型)可以是多於一個CV-合格
  • A可以是另一個指向成員類型的指針或指針,可通過限定轉換(conv.qual)轉換爲推導出的A.
  • 如果P是一個類,並且P具有template-id形式,那麼A可以是推導出的A的派生類。同樣,如果P是指向表單template-id的類的指針,則A可以是一個指針指向一個派生類通過推導A.

這裏指出,「P」是模板函數的參數類型:M B::*p,其中模板類型參數M將被確定。 「A」是實際參數的類型:int A::*。 P和A當然不是一個引用或類,我們需要這種工作所需的指針到成員的轉換不是一種限定轉換(它只描述了常量/易失性操作,如到const X*int X::*const int X::* )。

因此無法推導出模板參數,您應該將<int>顯式模板參數添加到您的代碼中。

+0

當您還需要轉換時,扣除無法進行。在這和Dummy的評論之間,我認爲這個回答很好。 +1 – Potatoswatter 2010-10-04 23:16:28

+0

實際上,在幾種情況下允許模板參數推導和參數轉換。例如,'template void f(const T *); void g(int * ptr){f(ptr); }'是合法的,編譯器必須推導'T'是'int'並將'ptr'轉換爲'const int *'。 – aschepler 2010-10-04 23:59:52