6

Working on a project I did not initiate, I want to add an << operator to a class. Problem: the class is a private inner class of an other class, the latter being in a namespace.

And I cannot make it.

The problem can be simplified this way:

#include <iostream>
#include <map>
namespace A {
    class B {
        private:
            typedef std::map<int, int> C;
            C a;
            friend std::ostream& operator<<(std::ostream& os, const C &c) {
                for (C::const_iterator p = c.begin(); p != c.end(); ++p)
                    os << (p->first) << "->" << (p->second) << " ";
                return os;
            }
        public:
            B() {
                a[13] = 10;
                std::cout << a << std::endl;
            }
        };
}
int main() {
    A::B c;
}

I try to compile it with g++ test.cpp: error: no match for ‘operator<<’. The compiler did not find my overloaded function. I thought it would have been simpler to define it in the header, with no luck. If you think it is more appropriate, I could also define the class in the CPP file, but I do not know how to do.

Last requirement, I cannot use C++11 (unfortunately).

unamourdeswann
  • 445
  • 3
  • 14
  • Your code works with Visual C++ compiler version 15.0 (i.e. VS2008, pre C++11). Which compiler are you using? Doesn't work on ideone.com.... – Tony Delroy Jan 14 '14 at 08:53
  • I don't see an inner class there. Just a regular class in a namespace. – RedX Jan 14 '14 at 08:56
  • @TonyD: Good question, I updated the text accordingly. I used plain g++: gcc version 4.8.1 (Ubuntu/Linaro 4.8.1-10ubuntu9). – unamourdeswann Jan 14 '14 at 08:57
  • Can `typedef`s be found that way by ADL ? What if you replace the `typedef` with a new class definition ? (just for testing) – ereOn Jan 14 '14 at 09:01
  • @RedX: Is that so? I thought that class `C`, for which I am trying to overload `<<`, could be considered as an inner class of `B`. Do you have suggestion for improvement of the formulation? – unamourdeswann Jan 14 '14 at 09:01
  • possible duplicate of [Access friend function defined in class](http://stackoverflow.com/questions/7785886/access-friend-function-defined-in-class) - In particular [this answer](http://stackoverflow.com/a/7785963/1171191) tells us you need a *declaration* of the `operator<<` outside the class. However, this means you will also need to define the constructor outside the class. – BoBTFish Jan 14 '14 at 09:06
  • @ereOn: If I replaced the `typedef ... C` with a `class C`, I would define the `<<` operator inside the class `C`, no? – unamourdeswann Jan 14 '14 at 09:06
  • 2
    @user980053: No, it's not an inner class, it's a type alias for `std::map`. So ADL only considers `namespace std`, and doesn't find your operator in `namespace A`. – Mike Seymour Jan 14 '14 at 09:08
  • @MikeSeymour: Right! I got it. So it is actually a duplicate of the [other post](http://stackoverflow.com/questions/7785886/access-friend-function-defined-in-class). But do you know how to define the operator in the CPP file? – unamourdeswann Jan 14 '14 at 09:14
  • @user980053: It doesn't matter: `friend` functions are anyway declared as if they were declared in the first enclosing non-class scope. The only difference in defining them in the class directly is that you have access to the class members without explicit qualification (and that the function is only accessible via ADL). – ereOn Jan 14 '14 at 09:15

2 Answers2

8

Since the friend operator is first declared inside the class, it's only available by argument-dependent lookup. However, neither of its parameter types are in namespace A, so it won't be found. C is an alias for std::map, so is considered to be in namespace std for the purposes of ADL.

There are various ways you could fix it, none of which are perfect:

  • Declare the function in namespace A before the class definition; then it becomes available by normal lookup, not just ADL. However, this breaks the encapsulation somewhat, and might cause problems if anything else tries to overload operator<< for std::map.
  • Replace the operator overload with a named static (not friend) function, and call it by name.
  • Declare C as an inner class, rather than an alias for std::map. This enables ADL without breaking encapsulation, but is a bit awkward if you want it to behave just like std::map.
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Looks good! But I replaced `std::cout << a << std::endl;` by `A::operator<<(std::cout, a);`, and I got `error: ‘operator<<’ is not a member of ‘A’`. Did I do it wrong? – unamourdeswann Jan 14 '14 at 09:25
  • @user980053: Sorry, now I think about it that won't work either; the operator can *only* be found by ADL. Personally, I'd probably give up on the idea of overloading `<<` and write a named function like `static void B::print(std::ostream& os, const C &c)`. – Mike Seymour Jan 14 '14 at 09:27
  • @MikeSeymour: Why does it need to be found by anything when it's fully qualified? – Lightness Races in Orbit Jan 14 '14 at 10:08
  • @LightnessRacesinOrbit: I'm fairly sure that, since it's not declared in the namespace, it can *only* be found by ADL, even if fully qualified. But I've just about reached the limits of my understanding of name lookup, and my brain will melt if I try to think about it any more. – Mike Seymour Jan 14 '14 at 10:10
  • @MikeSeymour: Actually, come to think of it, that _does_ ring a bell, in that I remember being shocked and appalled by this ridiculous rule in the past ;) I'll spend max ten minutes looking for it in the standard then give up. – Lightness Races in Orbit Jan 14 '14 at 10:18
  • http://en.wikipedia.org/wiki/Barton%E2%80%93Nackman_trick#How_it_works <-- good enough. "friend name injection" is the key to this question, then. (I still can't find applicable language in §11.3, though.) – Lightness Races in Orbit Jan 14 '14 at 10:22
1

Based on Mike Seymour's answer, here's an example for the first solution. Note operator<<() should be defined outside of class B, and B::C's real type is exposed. It's not perfect but readable...

namespace A {

  // It has to expose the B::C's type
  std::ostream& operator<<(std::ostream& os, const std::map<int, int> &c);

  class B {
  private:
    typedef std::map<int, int> C;
    C a;
    friend std::ostream& operator<<(std::ostream& os, const B::C &c);
  public:
      B() {
        a[13] = 10;
        std::cout << a << std::endl;
      }
    };

  std::ostream& operator<<(std::ostream& os, const B::C &c) {
    for (B::C::const_iterator p = c.begin(); p != c.end(); ++p) {
      os << (p->first) << "->" << (p->second) << " ";
    }
    return os;
  }
}
Mine
  • 4,123
  • 1
  • 25
  • 46
  • Cool! The only drawback is that I am now defining the `operator<<` for every `map` of `namespace A`. I hoped that I could restrict the definition to `C`... – unamourdeswann Jan 14 '14 at 09:36