6

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?

Community
  • 1
  • 1
Adam
  • 63
  • 4
  • 1
    "First, define the `Window_mgr` class [...] Next, define class `Screen`" That sounds like `Window_mgr` shall not have (non-pointer non-reference) members of type `Screen` (or, in this case `std::vector`). – dyp Sep 19 '13 at 21:31
  • Yes, so that's what I was thinking as well, but clearly their definition of `clear` uses the vector they define in `Window_mgr`. It really seems like it's a badly thought out exercise. – Adam Sep 19 '13 at 21:35
  • 1
    I supposed using a `std::shared_ptr` vector is off the table, eh. – WhozCraig Sep 19 '13 at 21:45
  • Even if using a `std::shared_ptr`, the `Window_mgr` class, as designed by the authors, initializes the vector with a default screen and would need to have a defined non-default constructor, no? – Adam Sep 19 '13 at 21:48
  • 1
    @Adam You can define your own default constructor, but you had to define it yourself, after the definition of `Screen`. – dyp Sep 19 '13 at 21:51
  • You can do it regardless. The authors default can be moved to the initializer list of the WindowMgr constructor. [See It Live](http://ideone.com/3tLXyl) – WhozCraig Sep 19 '13 at 21:52
  • I think this might be a bit more complicated. The question is whether or not incomplete types are allowed as template argument for `std::vector`, and I can't find anything specific about it in the Standard atm. – dyp Sep 19 '13 at 22:00
  • Collecting some hints from [here](http://stackoverflow.com/a/18672346/420683), [here](http://stackoverflow.com/q/8329826/420683) and [here](http://stackoverflow.com/q/3074872/420683), it seems the solution to this problem relies on undefined behaviour. The Standard seems not to allow incomplete types as `value_type` for `std::vector`, but only for historic reasons. Some (most?) of the recent major implementations of the StdLib however do support incomplete `value_type`s (at least for `vector`). – dyp Sep 19 '13 at 22:12
  • @DyP I learned what I could from this exercise. I wouldn't use such a design really, it was more of an academic exercise. `clear` belongs in `Screen` and `Screen` needs to be defined before `Window_mgr`. That side-steps the whole friends issue, and the vector issue. – Adam Sep 19 '13 at 23:16

1 Answers1

3

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);
}
Community
  • 1
  • 1
BЈовић
  • 57,268
  • 38
  • 158
  • 253
  • @DyP Leaving to OP to do his homework, I added another example, which demonstrates what I meant. It has to be defined. – BЈовић Sep 19 '13 at 21:41
  • I understand that a class needs to be defined before you can declare members of it as friends. In your second example, you defined B before declaring a member of B as a friend in A. The problem I have is that I cannot define the equivalent of B first, because it has a vector of A. – Adam Sep 19 '13 at 21:43
  • @Adam I don't see why not. See how I solved your exercise in the latest edit – BЈовић Sep 19 '13 at 21:53
  • I'm astonished this works. I don't see any rule allowing `std::vector` to have an incomplete `value_type`, although I can imagine how it internally works. See [this question](http://stackoverflow.com/q/8329826/420683) – dyp Sep 19 '13 at 21:53
  • I'm astonished it worked. I assumed the compiler saying I was using an incomplete type for Screen on the Vector line to be the Vector itself, but it was the call to the constructor it didn't like. – Adam Sep 19 '13 at 22:01
  • @DyP Yes, I have seen similar questions, but I think that the type is completely defined at the moment of instantiation. – BЈовић Sep 19 '13 at 22:01
  • 1
    @DyP I was too, until I realized `std::vector` uses `Screen` internally for pointer and reference decls; *not* as an instance. Prove this by removing the vector and just putting `Screen screen;` there. It will break immediately. – WhozCraig Sep 19 '13 at 22:02
  • @WhozCraig Quoting myself ;) "I can imagine how it internally works" The question is if it's guaranteed to work, UB or ill-formed. – dyp Sep 19 '13 at 22:03
  • @BЈовић "I think that the type is completely defined at the moment of instantiation" Well, what is the point of instantiation here? I think [temp.point]/3 applies here "Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization." – dyp Sep 19 '13 at 22:04
  • @DyP I can/would hardly believe `std::vector<>` is that defined. And I like you was astonished that `std::vector`::value_type is even *available* (and now that I think about it, I don't think it is... yet). – WhozCraig Sep 19 '13 at 22:04
  • @WhozCraig Ah! I knew I've seen this just recently. See [this answer](http://stackoverflow.com/a/18672346/420683). There's a hint in the Standard under a section about functions, but it seems it indeed is UB. – dyp Sep 19 '13 at 22:08
  • 1
    @DyP yeah, i'd rather not rely on it just on that alone. If I was told to do this I'd quietly go into that good night with a `std::vector>`. At least I'm *sure* thats not UB. (whether this is, or isn't, I can't claim to be intimate enough with the standard to swing a gavel). – WhozCraig Sep 19 '13 at 22:10
  • 1
    @BЈовић "I think that the type is completely defined at the moment of instantiation" For the member functions of `std::vector`, yes. They're only implicitly instantiated when called, and at that time (in this example) the type `Screen` is complete (that's why it works). However, for the class template specialization `std::vector` itself (i.e., the type itself), the point of instantiation is *before* `Screen` is complete. – dyp Sep 19 '13 at 22:15
  • @DyP It is easily demonstrable that forcing that instantiation early can/will break this. I.e. `std::vector::value_type m_scr;` will obviously not work. – WhozCraig Sep 20 '13 at 00:24
  • @BЈовић How to separate the two classes into two files? Thank you! – Sparks_Fly Jan 07 '15 at 14:32