1

I'm trying to overload operator<< in the standard way. I have a class called SymbolTable residing in a file called SymbolTable.h as follows:

namespace Compaler // It's a pun; don't ask
{

class SymbolTable
{
public:
  // ...
  friend ostream& operator<<(ostream &out, const SymbolTable &table);
private:
  vector<map<int, Symbol*> > mSymbols;
};

// ...

}

The implementation of operator<< accesses some private members of SymbolTable. Here is the signature of the implementation, to demonstrate that it does not differ from the signature of the forward declaration (I copy-pasted it to be sure I wasn't going crazy.)

ostream& operator<<(ostream &out, const SymbolTable &table)
{ ... }

If I put the implementation in SymbolTable.h where the second ... is, I get the following linker error:

ld: duplicate symbol Compaler::operator<<(std::basic_ostream >&, Compaler::SymbolTable const&)in /var/folders/kt/pl6qd1490fn3yqxfpg64jyr80000gn/T//ccstrYnU.o and /var/folders/kt/pl6qd1490fn3yqxfpg64jyr80000gn/T//ccDQFiyK.o for architecture x86_64

However, if I instead put it in SymbolTable.cpp, where the rest of my SymbolTable member implementations are, the code won't even compile; I get errors related to accessing private members, indicating that the friendship is not being recognized:

../SymbolTable/SymbolTable.h: In function ‘std::ostream& operator<<(std::ostream&, const Compaler::SymbolTable&)’: ../SymbolTable/SymbolTable.h:75: error: ‘std::vector, std::allocator >, Compaler::Symbol*, std::less, std::allocator > >, std::allocator, std::allocator >, Compaler::Symbol*> > >, std::allocator, std::allocator >, Compaler::Symbol*, std::less, std::allocator > >, std::allocator, std::allocator >, Compaler::Symbol*> > > > > Compaler::SymbolTable::mSymbols’ is private ../SymbolTable/SymbolTable.cpp:98: error: within this context

What am I doing wrong?

Summary of answers:

Thanks to Seth Carnegie and Daniel R. Hicks, I've got it figured out. For the benefit of anyone who might make the same mistakes in the future, I'm going to summarize the answers in my question because there was a lot of back and forth in the comments.

The first (duplicate symbol when the implementation was in SymbolTable.h) problem occurred because I was trying to simultaneously compile and link two files which included SymbolTable.h . I have been labouring under a misunderstanding of how #ifdef include guards work; that is, they will only prevent code from being included twice in one file, but will not help you if you try to link two files which both included code from a third.

The second problem was that I forgot to put my operator<< implementation in the Compaler namespace when I had it in the SymbolTable.cpp file.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Mitch Lindgren
  • 2,120
  • 1
  • 18
  • 36
  • Add `inline` to the function definition in the header file, that should make the compiler shut up about the multiple definition. – K-ballo Nov 06 '11 at 02:26
  • 2
    Are you sure you got the namespace right? As a workaround you can put the function definition right inside the class definition, together with the friendship line... but that's not an answer. – Kerrek SB Nov 06 '11 at 02:26
  • 1
    In the first case you get the duplicate symbol error because SymbolTable.H is included in more than one .C file. – Hot Licks Nov 06 '11 at 02:27
  • 1
    Looks to me like you've included it in both SymbolTable.cpp and SymbolTableTest.cpp. – Hot Licks Nov 06 '11 at 02:33
  • 1
    Stupid question: When you put your `operator <<` definition in a .CPP file, do you appropriately specify the namespace with `SymbolTable`? If you don't give the namespace somehow that occurrence of `SymbolTable` will appear to be entirely different from the one in your .H file. – Hot Licks Nov 06 '11 at 02:37
  • Daniel, you are correct. I deleted my comment. – Mitch Lindgren Nov 06 '11 at 02:38
  • Daniel: ugh, that was the problem with putting it in the .cpp file... didn't declare it to be within the right namespace. I feel really foolish. – Mitch Lindgren Nov 06 '11 at 02:40
  • @MitchLindgren you should probably ask Kerrek or Daniel to post an answer and accept it, instead of mine which didn't answer your question. – Seth Carnegie Nov 06 '11 at 02:43
  • @Seth Carnegie - you did answer at least half of the question in that you made me realize my mistake regarding the `#ifdef` guards. However, if Daniel would like to post an answer and you still feel that he is more deserving, then I will happily accept that one. Additionally, since there has been so much discussion in the comments, I have summarized everything in an edit to my question for the benefit of future readers. Thanks very much to all of you. – Mitch Lindgren Nov 06 '11 at 02:51

3 Answers3

3

If you do it in the header file you need to use inline:

namespace Compaler // It's a pun; don't ask
{    
    class SymbolTable
    {
        public:
            friend ostream& operator<<(ostream &out, const SymbolTable &table);
        private:
            vector<map<int, Symbol*> > mSymbols;
    };

    inline ostream& operator<<(ostream &out, const SymbolTable &table)
    {
         out << table[0].something;
         return out;
    }
}

If you want to do in the source file. Then the operator<< must be declared in the namespace

Header.h

namespace Compaler // It's a pun; don't ask
{    
    class SymbolTable
    {
        public:
            friend ostream& operator<<(ostream &out, const SymbolTable &table);
        private:
            vector<map<int, Symbol*> > mSymbols;
    };
    ostream& operator<<(ostream &out, const SymbolTable &table);
}

Source.cpp

#include "Header.h"
namespace Compaler   // must be here
{    
    ostream& operator<<(ostream &out, const SymbolTable &table)
    {
         out << table[0].something;
         return out;
    }
}
Community
  • 1
  • 1
Martin York
  • 257,169
  • 86
  • 333
  • 562
2

The first problem is because you're putting the definition in the header file and #includeing it twice. This causes a multiple definition error.

As for the second problem, probably your function has a different signature than the friend declaration.

Just to let you know, as an alternative to getting the signature right, you can write the function inline:

class SymbolTable
{
public:
  // ...
  friend ostream& operator<<(ostream &out, const SymbolTable &table) {
      ...
  }
private:
  vector<map<int, Symbol*> > mSymbols;
};

Edit: Since apparently the signature is right, the error lies somewhere else. You'll have to post more code.

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • The header file has `#ifdef` guards to prevent double inclusion, and in any case, I am 100% sure it's only being included once as I'm just trying to compile a simple unit test right now. I'm guessing you started typing your answer before I edited the question... I included the signature of the implementation; I copy pasted it from the forward declaration and have gone over it repeatedly to ensure it is the same. – Mitch Lindgren Nov 06 '11 at 02:26
  • @MitchLindgren `#ifdef` guards don't help in this case; those only prevent the file from being included more than once in the same file, not in seperate files. For instance, if you included it twice in `a.cpp`, the `#ifdef` guards would kick in, but if you included it once each in both `a.cpp` and `b.cpp`, the `#ifdef`s wouldn't make a difference and you'd get a linker error. – Seth Carnegie Nov 06 '11 at 02:29
  • @MitchLindgren also apparently your code is not in `SymbolTable.cpp` when you get the errors about accessing private members, because the error says `SymbolTable.h`. – Seth Carnegie Nov 06 '11 at 02:30
  • Oops, you're right. I see what is causing the duplicate symbol problem now. My bad. Regarding the errors about accessing private members, I missed a line in the output. I have edited my question to include it. – Mitch Lindgren Nov 06 '11 at 02:33
  • 1
    Note that #ifdefs will not prevent the inclusion of the .H in two different .CPP files. – Hot Licks Nov 06 '11 at 02:38
2

As other answers and comments have pointed out, it is possible to do this as a friend function. However, an alternative I often prefer is to make a public print function, which prints the object to an std::ostream, and then let operator<< call that function. This lets you avoid breaking encapsulation by making operator<< a friend function:

namespace Compaler // It's a pun; don't ask
{

class SymbolTable
{
public:
  // ...
  void print(ostream & out); // basically your definition of operator<<, in your .cpp file
private:
  vector<map<int, Symbol*> > mSymbols;
};

// this doesn't have to be inline, but since it's a two-liner, there's probably no harm either
inline ostream& operator<<(ostream &out, const SymbolTable &table)
{
    table.print(out);
    return out;
}

// ...

}

This also gives your clients the option to avoid the << syntax, which may or may not be a good thing.

Another trick: if you do this for multiple classes, you could make operator<< a template, so that any type implementing print(ostream&) can be output to a stream:

template <typename T>
inline ostream& operator<<(ostream &out, const T &obj)
{
    obj.print(out);
    return out;
}

Since this is a template function, any specific definitions of it for a class will override this definition, and classes that don't define print(ostream&) are fne as long as they are never output to a stream, since this function will never be instantiated in that case.

Aria Buckles
  • 923
  • 5
  • 10
  • 1
    Makeing friends does not break encapsulation. It increases encapsulation and documents tight bonding of objects. See [programers](http://programmers.stackexchange.com/q/99589/12917) and [stackoverflow](http://stackoverflow.com/questions/1093618/how-does-the-friend-keyword-class-function-break-encapsulation-in-c/1093681#1093681) – Martin York Nov 06 '11 at 17:22
  • 1
    Though I have no problem with a print() method (I do that sometimes). clients the option to avoid the << syntax Is like saying that C users can avoid pointers. Understanding the stream operators is fundamental to C++ and makes the STL librry very useful (see [std::istream_iterator](http://www.sgi.com/tech/stl/istream_iterator.html)). – Martin York Nov 06 '11 at 17:25