c++templatesprotectedderivedsafe-bool-idiom

Weird compiler error and template inheritance


Could someone explain me why this code:

class safe_bool_base
{ //13
    protected:

        typedef void (safe_bool_base::*bool_type)() const;

        void this_type_does_not_support_comparisons() const {} //18

        safe_bool_base() {}
        safe_bool_base(const safe_bool_base&) {}
        safe_bool_base& operator=(const safe_bool_base&) { return *this; }
        ~safe_bool_base() {}
};

template <typename T=void> class safe_bool : public safe_bool_base
{
    public:

        operator bool_type() const
        {
            return (static_cast<const T*>(this))->boolean_test() ? &safe_bool_base::this_type_does_not_support_comparisons : 0;
        }

    protected:

        ~safe_bool() {}
};

template <> class safe_bool<void> : public safe_bool_base
{
    public:

        operator bool_type() const
        {
            return (boolean_test() == true) ? &safe_bool_base::this_type_does_not_support_comparisons : 0; //46
        }

    protected:

        virtual bool boolean_test() const = 0;
        virtual ~safe_bool() {}
};

Produces the following compiler error ?

c:\project\include\safe_bool.hpp(46) : error C2248: 'safe_bool_base::this_type_does_not_support_comparisons' : cannot access protected member declared in class 'safe_bool_base'
c:\project\include\safe_bool.hpp(18) : see declaration of 'safe_bool_base::this_type_does_not_support_comparisons'
c:\project\include\safe_bool.hpp(13) : see declaration of 'safe_bool_base'

Since both safe_bool templates derive from safe_bool_base, I don't understand why one can't access a protected member of the base class.

Am I missing something ?


Solution

  • This should probably help (reproducible in a non template situation also)

    struct A{
    protected:
        void f(){}
    };
    
    struct B : A{
        void g(){&A::f;}        // error, due to Standard rule quoted below
    };
    
    int main(){
    }
    

    VS gives "'A::f' : cannot access protected member declared in class 'A'"

    For the same code, Comeau gives

    "ComeauTest.c", line 7: error: protected function "A::f" (declared at line 3) is not accessible through a "A" pointer or object void g(){&A::f;} ^

    "ComeauTest.c", line 7: warning: expression has no effect void g(){&A::f;}

    Here is the fixed code which achieves the desired intentions

    struct A{
    protected:
        void f(){}
    };
    
    struct B : A{
        void g(){&B::f;}        // works now
    };
    
    int main(){
    }
    

    So, why does the first code snippet not work?

    This is because of the following rule in the C++ Standard03

    11.5/1- "When a friend or a member function of a derived class references a protected nonstatic member function or protected nonstatic data member of a base class, an access check applies in addition to those described earlier in clause 11.102) Except when forming a pointer to member (5.3.1), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class) (5.2.5). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).

    So change the return within operator functions as follows

    return (boolean_test() == true) ? &safe_bool<void>::this_type_does_not_support_comparisons : 0; //46 
    
    return (static_cast<const T*>(this))->boolean_test() ? &typename safe_bool<T>::this_type_does_not_support_comparisons : 0; 
    

    EDIT 2: Please ignore my explanations. David is right. Here is what it boils down to.

    struct A{
    protected:
        int x;
    };
    
    struct B : A{
        void f();
    };
    
    struct C : B{};
    
    struct D: A{            // not from 'C'
    };
    
    void B::f(){
        x = 2;         // it's own 'A' subobjects 'x'. Well-formed
    
        B b;
        b.x = 2;       // access through B, well-formed
    
        C c;
        c.x = 2;       // access in 'B' using 'C' which is derived from 'B', well-formed.
    
        D d;
        d.x = 2;       // ill-formed. 'B' and 'D' unrelated even though 'A' is a common base
    }
    
    int main(){}