Search code examples
c++header-filesfriend

Why can't friend function be recognized with using directive vs enclosing in namespace?


Why can't the friend operator<< be recognized when defined in the cpp file and a using directive? I have in header file, game.h

namespace uiuc {

class Game {
public:
  Game();
  void solve();

  friend std::ostream & operator<<(std::ostream & os, Game & game);

private:
  std::vector<Stack> stacks;
};

And in my cpp file, game.cpp:

#include "game.h"

using namespace  uiuc;
std::ostream & operator<<(std::ostream & os, Game & game) {
  for (unsigned long i = 0; i < game.stacks.size(); ++i) {
    os << "Stack [" << i << "]: " << game.stacks[i] << std::endl;
  }

  return os;
}

The error I get is:

g++ -std=c++1z -g -Wfatal-errors -Wall -Wextra -pedantic -MMD -MP  -c game.cpp
game.cpp:5:38: fatal error: 'stacks' is a private member of 'uiuc::Game'
  for (unsigned long i = 0; i < game.stacks.size(); ++i) {
                                     ^
./game.h:17:22: note: declared private here
  std::vector<Stack> stacks;
                     ^
1 error generated.
make: *** [Makefile:18: game.o] Error 1

This worked when the friend function was defined in the header file. I decided to move it since I was getting a linker error for a duplicate symbol for the same method so I decided to see what would happen if I moved the function definition to the cpp file. What am I missing? However when I enclose the definition within the namespace uiuc, it removes this error and I am back to the linker error. The linker error is NOT what this question is about.

Why can't the compiler not realize that this is a friend function and so can access the private variables?


Solution

  • Functions first declared as friend in a class are placed in the enclosing namespace, so the function you want to define must be defined in the uiuc namespace.

    You might be under assumption that if you use using namespace uiuc; new declarations/definitions will be placed into the uiuc namespace, but that is not the case. using namespace only affects the lookup of names, not where declarations/definitions are placed.

    Therefore the function you are defining at the moment is operator<<(std::ostream & os, uiuc::Game & game) in the global namespace scope (which is not a friend of uiuc::Game), not operator<<(std::ostream & os, Game & game) in the uiuc namespace.

    So you need to open the namespace properly:

    #include "game.h"
    
    namespace uiuc {
      std::ostream & operator<<(std::ostream & os, Game & game) {
        for (unsigned long i = 0; i < game.stacks.size(); ++i) {
          os << "Stack [" << i << "]: " << game.stacks[i] << std::endl;
        }
    
        return os;
      }
    }
    

    Also, regarding the linker error: If you define the friend function outside the class definition and you don't specify it as inline, then it will be a non-inline function, meaning that there may be only one definition for it in only one translation unit. This typically precludes putting the definition in the header, because the header is usually included in multiple translation units.

    If you define the function inside the class body or declare it with the inline keyword, then it will be an inline function, meaning that it has to be defined in every translation unit using it, which usually means that the definition must be placed in the header file.