c++templatesinheritancetemplate-specializationsafe-bool-idiom

Protected member access error in template specialization


class safe_bool_base {
protected:
  void this_type_does_not_support_comparisons() const {}
};

template <typename T=void> class safe_bool : public safe_bool_base {
public:
  void func() {
    &safe_bool::this_type_does_not_support_comparisons;
    &safe_bool_base::this_type_does_not_support_comparisons;
  }
};

template<> class safe_bool<void> : public safe_bool_base {
public:
  void func() {
    &safe_bool::this_type_does_not_support_comparisons;
    &safe_bool_base::this_type_does_not_support_comparisons;
  }
};

Error Message:

zzz.cpp: In member function 'void safe_bool<void>::func()':
zzz.cpp:7:10: error: 'void safe_bool_base::this_type_does_not_support_comparison
s() const' is protected
 void this_type_does_not_support_comparisons() const {}
      ^
zzz.cpp:22:24: error: within this context
   &safe_bool_base::this_type_does_not_support_comparisons;
                    ^

I wonder why protected member can not be visited in the template specialization. The codes are meaningless and just for testing.


Solution

  • When public inherite from the base class, its protected members become the derived class' protect members, which could be accessed in derived class' member functions. Note they're only accessed through the derived class itself (and its derived classes). But the protected members can't be accessed through the base class. That why &safe_bool::this_type_does_not_support_comparisons; works but &safe_bool_base::this_type_does_not_support_comparisons; doesn't.

    From the standard, 11.4/1 Protected member access [class.protected]:

    (emphasie mine)

    An additional access check beyond those described earlier in Clause [class.access] is applied when a non-static data member or non-static member function is a protected member of its naming class ([class.access.base])114 As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member ([expr.unary.op]), the nested-name-specifier shall denote C or a class derived from C. All other accesses involve a (possibly implicit) object expression ([expr.ref]). In this case, the class of the object expression shall be C or a class derived from C. [ Example:

    class B {
    protected:
      int i;
      static int j;
    };
    
    class D1 : public B {
    };
    
    class D2 : public B {
      friend void fr(B*,D1*,D2*);
      void mem(B*,D1*);
    };
    
    ...
    void D2::mem(B* pb, D1* p1) {
      pb->i = 1;                    // ill-formed
      p1->i = 2;                    // ill-formed
      i = 3;                        // OK (access through this)
      B::i = 4;                     // OK (access through this, qualification ignored)
      int B::* pmi_B = &B::i;       // ill-formed
      int B::* pmi_B2 = &D2::i;     // OK
      j = 5;                        // OK (because j refers to static member)
      B::j = 6;                     // OK (because B::j refers to static member)
    }
    
    ...
    

    ā€” end example ]

    Note the statement int B::* pmi_B = &B::i; // ill-formed in the sample code from the standard, basically it's the same case of your code. BTW it has nothing to do with template specialization.