Search code examples
c++classfriendforward-declaration

How to declare two classes such that A has members of B and B marks members of A as friends?


I am attempting to do exercise 7.32 from C++ Primer 5th Edition. That exercise asks the following:

Define your own versions of Screen and Window_mgr in which clear is a member of Window_mgr and a friend of Screen.

Here are the definitions for Screen, Window_mgr and clear given in the text.

class Screen
{
  public:
    using pos = std::string::size_type;
    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) { }
  private:
    pos height = 0, width = 0;
    std::string contents;
};

class Window_mgr
{
  public:
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
  private:
    std::vector<Screen> screens{Screen(24, 80 ' ')};
};

void Window_mgr::clear(ScreenIndex i)
{
  Screen &s = screens[i];
  s.contents = std::string(s.height * s.width, ' ');
}

Now those two classes, if defined Screen first than Window_mgr work as I expect. Now, the exercise asks me to make clear a friend of Screen and define clear. To make clear a member a friend, if I understand correctly, Window_mgr must be defined. To define Window_mgr, Screen must be defined. This seems impossible to me.

The text gives the following hints:

Making a member function a friend requires careful structuring of our programs to accommodate interdependencies among the declarations and definitions. In this example, we must order our program as follows:

  • First, define the Window_mgr class, which declares, but does not define, clear. Screen must be declared before clear can use members of Screen.

  • Next, define class Screen, including a friend declaration for clear.

  • Finally, define clear, which can now refer to members in Screen.

The order in which I attempted to solve this exercise was ultimately this:

class Screen;

class Window_mgr
{
  public:
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
  private:
    std::vector<Screen> screens{Screen(24, 80 ' ')};
};

class Screen
{
  friend Window_mgr::clear(Window_mgr::ScreenIndex);
  public:
    using pos = std::string::size_type;
    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) { }
  private:
    pos height = 0, width = 0;
    std::string contents;
};

void Window_mgr::clear(ScreenIndex i)
{
  Screen &s = screens[i];
  s.contents = std::string(s.height * s.width, ' ');
}

This obviously would not work, due to the vector in Window_mgr that needs Screen to be a complete type. This seems like an unsolvable exercise, unless the authors do not intend one to use Screen and Window_mgr classes they present earlier.

Has anyone else solved this exercise from C++ Primer. If so, how? Any help how this can be done, or as my gut tells me, cannot be done?


Solution

  • As [class.friend]/5 says :

    When a friend declaration refers to an overloaded name or operator, only the function specified by the parameter types becomes a friend. A member function of a class X can be a friend of a class Y.

    In your specific case :

    #include <iostream>
    #include <vector>
    
    struct Screen;
    
    class Window_mgr
    {
      public:
    
        Window_mgr();
    
        using ScreenIndex = std::vector<Screen>::size_type;
        void clear(ScreenIndex);
      private:
        std::vector<Screen> screens;
    };
    
    class Screen
    {
      friend void Window_mgr::clear(ScreenIndex);
      public:
        using pos = std::string::size_type;
        Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) { }
      private:
        pos height = 0, width = 0;
        std::string contents;
    };
    
    
    Window_mgr::Window_mgr():
      screens{1, Screen(24, 80, ' ') }
    {
    }
    
    void Window_mgr::clear(ScreenIndex i)
    {
      Screen &s = screens[i];
      s.contents = std::string(s.height * s.width, ' ');
    }
    
    int main()
    {
      Window_mgr w;
      w.clear(0);
    }
    

    Take a note that it is not possible to solve that exercise, because Window_mgr has a member variable of std::vector which argument is an incomplete type. It will work on most compilers (see here why), but the standard prohibits it.

    This example demonstrates how to make a member function friend of a class :

    #include <iostream>
    
    struct A;
    
    struct B
    { 
      void bar( A& a, int l);
    };
    
    struct A
    {
      friend void B::bar(A&,int);
      A():k(0){}
      private:
      void foo(int m);
      int k;
    };
    
    
    
    void A::foo(int m)
    {
      std::cout<<"A::foo() changing from "<<k<<" to "<<m<<std::endl;
      k=m;
    }
    
    void B::bar( A& a, int l)
    {
      std::cout<<"B::bar() changing to "<<l<<std::endl;
      a.foo(l);
    }
    
    int main()
    {
      A a;
      B b;
      b.bar(a,11);
    }