Refined friend(s)

  Document number:
Date:
Reply to:

NXXXX
2015/03/22
Dejan D.M. Milosavljevic
(dmilos at gmail dot com)
 
Table of Contents.
I. Abstract
Making friends allows them to use private and protected members.
This refinement restrict accessibility in two ways 'access only to' or 'access to all except to'.
II. Motivation
protected and private make limitation of access to class members.
friend make exception to that by granting complete access.
In some cases there is need to limit access or to have something like 'grant access only to' or 'grant access to all except to'.
Problem:
[Example:
 class A{
   public:
     void set_value( int const& val );
     int const& get_value()const;

   protected:
     void val_internal_set( int const& i );
   private:
     int int_value;
   friend class B;
  };

 class B {
   public:
    void val_change( A & a ){
        a.val_internal_set( 2 );
        a.int_value = 4;     //!< We do not want that, but it can be accidentally on this place even with correct effects.
    }
 };
end example]

Slightly refined, but B::val_change, has complete access.
Problem still exists. Compiler will not emit error. In this case we actuality need limitation in opposite direction.
[Example:
 class A{
   // ... ... ...
   friend class B::val_change;
  };
end example]
In here, refinement is not enough and not good. class B still can access to A::val_internal_set.
We want to grant access only to A::val_internal_set and not to other members.
This will allow us to appropriately implement B::val_change knowing that ( by design ) class B ( or B::val_change) can access to A::val_internal_set and not to other members.

III. Solution
Exact refinement by explicit expressing which members/functions friends can access or not.
[Example:
class A{
  // ... ... ...

  protected:
   void val_internal_set( int const& i );
  private:
   int int_value;

  protected:
   void float_internal_set( float const& f );
  private:
   float float_value;

  protected:
   void string_internal_set( std::string const& s );
  private:
   std::string string_value;

   // B::val_change can use only to val_internal_set and float_internal_set.
   friend void B::val_change() >> void val_internal_set( int const& i ) 
                               >> void float_internal_set( float const& f );

   // C::val_change() can use only to float_internal_set.
   friend void C::val_change() >> void float_internal_set( float const& f );

   // D::val_change() can not use string_value but can use other members.
   friend void D::val_change() !>> string_value;
 };

 class B {
   public:
    void val_change( A & a ){
        a.val_internal_set( 4 );  //!< OK
        a.int_value = 2;          //!< Error can not use
     }
  };

 class C {
   public:
    void val_change( A & a ){
        a.float_internal_set( 4 );   //!< OK
        a.val_internal_set( 2 );     //!< Error can not use
    }
  };

 class D {
   public:
    void val_change( A & a ){
        a.val_internal_set( 4 );   //!< OK
        a.float_internal_set( 2 ); //!< OK
        a.string_value = "abc";      //!< Error can not use
    }
  };
end example]
Pattern Refinement and mixing of '>>' and '!>>'

In brief: Bad idea!

[Example:
 class A{
   // Access to all protected functions and to string_value
   friend class X >> protected !>> string_value;
   // Access to all private functions and not to string_value
   friend class X >> private !>> string_value;
 };
end example]

This kind of refinement tend to be code bloating. Can easily cause mistakes during code inspection/review like misunderstanding.
During developing this refinement can be to wide to catch/control our intention/design.
In this case I think that it is better to make this simple to to level of 'grant access only to' or 'grant access to all except to'.
Eve further mixing of '>>' and '!>>' in any way can make only confusion of what type of refinement we want e.g. make statement ambiguous.

IV. Syntax
friend-declaration:
friend friend-specifier ( '>>' friend-member-declaration )zero-or-more ';'
friend friend-specifier ( '!' '>>' friend-member-declaration )zero-or-more ';'
friend-specifier:
elaborated-type-specifier
simple-type-specifier
typename-specifier
friend-member-declaration:
data-member-declaration
function-member-declaration
type-member-declaration
enumerator-member-declaration
data-member-declaration:
identifier
function-member-declaration:
function-declarator
type-member-declaration
identifier
enumerator-member-declaration:
identifier
Note: '!>>' is not new token. It is two tokens '!' and '>>'.
Constraints
Friend refinement for some class/function shall appears only once.
[Example:
class A {
  public:
   //   ill-formed. Double friend refinement of B::val_change().
   friend void B::val_change() >> void val_internal_set( int & i ) 
   friend void B::val_change() >> void float_internal_set( int & i ) ;

   //   OK. This is not refinement.
   friend class E;
   friend class E;
 };
end example]
Effects:
  • Prevent abusing by making endless list.
  • Prevent of scattering one friend-specifier to several different places.
  • In case that list start to become large this will show obvious flaw in design.
Other Syntax That Fails
'->'
In here we obviously have problem with new function declaration syntax.
[Example:
class A {
  // ... ... ...
  friend void B::val_change() -> val_internal_set( int const& i ) -> void
                              -> float_internal_set( float const& f ) -> void;
  };
end example]
' >>' vs ','
Not so clearly visible/readable after we read first line.
[Example:
class A {
  // ... ... ...
  friend void B::val_change() >> void val_internal_set( int const& i )
                                , void float_internal_set( float const& f );
  };
end example]
'>> { }'
It can be misunderstand as function definition.
[Example:
class A {
  // ... ... ...
  friend void B::val_change() >> { void val_internal_set( int const& i )
                              , void float_internal_set( float const& f ); }
  };
end example]
Without >> this is more worse. It is look like function definition.
[Example:
class A {
  // ... ... ...
  friend void B::val_change() { void val_internal_set( int const& i )
                              , void float_internal_set( float const& f ); }
  };
end example]
'[ ..., ... ]'
Might be understand as array access.
[Example:
class A {
  // ... ... ...
  friend void B::val_change() >> [ void val_internal_set( int const& i )
                              , void float_internal_set( float const& f ) ] ;
  };
end example]
Without '>>' things look much worse.
[Example:
class A {
  // ... ... ...
  friend void B::val_change() [ void val_internal_set( int const& i )
                              , void float_internal_set( float const& f ) ] ;
  };
end example]

V. Summary
Access to all
By using friend specifier as usual.
No access at all
Jut do not use friend specifier.
Access to some members
By using '>>'
Not to access to some members
By using '!' '>>'
VI. Impact On the Standard
Core
No effect.
No new keywords.
No new tokens.
No changes to old syntax.
Pure extension.
Backward compatible. By design old declaration have same interpretation as described in ISO/IEC 14882 Second edition 2003-10-15.
Library
No effect.
Existing code.
No effect.