2017-10-14 56 views
2

以下代碼無法在Linux上使用GCC 7.2.0和Clang 5.0.0進行編譯。將指針指向從私有基類別別名的成員函數

#include <iostream> 

struct A 
{ 
    void f() 
    { 
     std::cout << "Hello, world!\n"; 
    } 
}; 

struct B : private A 
{ 
    using A::f; 
}; 

int main() 
{ 
    B b; 
    void (B::*f)() = &B::f; // Error: 'A' is an inaccessible base of 'B' 
    (b.*f)(); 
} 

這是否符合標準? B中的公開使用聲明不應允許透明地使用指向B::f的成員函數指針,而不是在B的角度之外涉及A::f的可訪問性?

+1

將有問題的行更改爲'auto f = &B::f;'會使該行進行編譯,但gcc會在下一行報告錯誤,(b。* f)();',抱怨:「error:'A'是「B」的難以接近的基礎「。嗯.... –

+0

@ SamVarshavchik:鏗鏘似乎表現相同的方式。 – 3442

回答

5

是的,你的程序不合格。

C++ 17(N4659)[namespace.udecl]/16(重點煤礦):

For the purpose of overload resolution, the functions that are introduced by a using-declaration into a derived class are treated as though they were members of the derived class. In particular, the implicit this parameter shall be treated as if it were a pointer to the derived class rather than to the base class. This has no effect on the type of the function, and in all other respects the function remains a member of the base class.

換句話說,所述using聲明不添加的B一個構件,它只是爲同一個成員添加第二個名字A::f。這個第二個名字可以通過名稱查找來選擇,並用於名稱使用的可訪問性檢查,但除此之外,除了重載解析所指出的,它與原始成員相同。

[expr.unary.op/3:

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static or variant member m of some class C with type T , the result has type "pointer to member of class C of type T " and is a prvalue designating C::m .

因此,即使合格-ID您使用的是拼寫的class B名稱和限定名稱查找B::f發現通過引入的名字所述using聲明B,實際函數表達式名稱是A的成員,所以表達式&B::f具有類型「指針)返回void(類型函數的A類的成員」,或作爲type-idvoid (A::*)()。您可以通過添加到您的例子驗證這一點:

#include <type_traits> 
static_assert(std::is_same<decltype(&B::f), void (A::*)()>::value, "error"); 

最後,在[conv.mem/2:

A prvalue of type "pointer to member of B of type cvT ", where B is a class type, can be converted to a prvalue of type "pointer to member of D of type cvT ", where D is a derived class of B . If B is an inaccessible, ambiguous, or virtual base class of D , or a base class of a virtual base class of D , a program that necessitates this conversion is ill-formed.

所以命名的成員函數指針是有效的,但是從void (A::*)()將其轉換到void (B::*)()不是,因爲繼承是從main無法訪問。

作爲一種變通方法,您可以提供訪問成員函數指針,除了成員函數本身B

struct B : private A 
{ 
    using A::f; 
    using func_ptr_type = void (B::*)(); 
    static constexpr func_ptr_type f_ptr = &A::f; 
}; 

或者,如果你真的必須使用C樣式轉換。在某些情況下,允許C風格的轉換忽略類繼承關係的可訪問性,其中C++風格轉換無法用相同的結果進行編譯。 (reinterpret_cast也可以將任何指向成員函數的指針轉換爲任何其他指針,但其結果未指定。)

int main() 
{ 
    B b; 
    void (B::*f)() = (void (B::*)()) &B::f; 
    (b.*f)(); 
} 

在連接到這一問題的意見。注意:如果你改變main

那麼f類型是void (A::*)()如上所述。但你碰上[expr.mptr.oper/2(重點煤礦再次):

The binary operator .* binds its second operand, which shall be of type "pointer to member of T " to its first operand, which shall be a glvalue of class T or of a class of which T is an unambiguous and accessible base class.

所以,你還是有問題的成員函數與原來的類關聯,不能被認爲是B的成員除B和任何朋友的範圍外。

+0

我試圖在現有的代碼庫中使用一些接口,這些接口使用「私有繼承和使用別名」模式相當多,顯然是爲了從客戶端強行隱藏庫自己的內部方法。也就是說,std :: thread t {&B :: f,&b };'這種形式的東西不會被編譯,因爲我不知道這個庫是否這樣做,並且沒有意識到標準的立場。幸運的是,我有機會改變圖書館。我想類似於內聯f(){return this-> A :: f(); }'是最透明和直接的解決方案,對吧? – 3442

+0

是的,創建傳遞函數是一個很好的解決方案。或者在使用地點,你可以做一些事情,比如'std :: thread t {[](B * b){b-> f(); },&b };' – aschepler