user4087080
user4087080

Reputation:

Friend functions and namespaces

I'm trying to overload the << operator for a class that's part of a user-defined namespace. What's interesting is that if I remove all that namespace stuff, the program compiles and runs without a problem, but the fact that the class resides in a namespace somehow makes the compilation process for the file A.cpp fail with an error saying that I'm trying to access private data in class A (demo.cpp compiles fine). Please, take a look at my three-file program and the compilation error that I get:

demo.cpp:

#include <iostream>
#include "A.h"


int main() {
    usr::A a(4);
    std::cout << a << std::endl;


    return 0;
}

A.h:

#ifndef A_H_
#define A_H_

#include <iostream>


namespace usr {
    class A {
        private:
            int m_x;
        public:
            A(int x);
            friend std::ostream& operator<<(std::ostream& os, const usr::A& a);
    };
}

#endif // A_H_

A.cpp:

#include "A.h"


usr::A::A(int x) : m_x(x) {}

std::ostream& operator<<(std::ostream& os, const usr::A& a) {
    os << a.m_x;
    return os;
}

Error:

$ g++ -c A.cpp
In file included from A.cpp:1:0:
A.h: In function ‘std::ostream& operator<<(std::ostream&, const usr::A&)’:
A.h:10:17: error: ‘int usr::A::m_x’ is private
             int m_x;
                 ^
A.cpp:7:13: error: within this context
     os << a.m_x;
             ^

Upvotes: 6

Views: 4017

Answers (2)

sdd
sdd

Reputation: 721

Under GCC you have to separate return-type and scope resolution operator (:: token) via some access modifier (reference on const or pointer).

For example, this will not compile under g++7.2.0:

std::string f(int a);

namespace NS
{
    class C
    {
        friend std::string ::f(int a);
        // scope operator  ^^ is absolutely needed
    }
}

But this will:

std::string f(int a);

namespace NS
{
    class C
    {
        friend std::string const ::f(int a);
        // see const keyword ^
    }
}

And this will:

std::string f(int a);

namespace NS
{
    class C
    {
        friend std::string& ::f(int a);
        // see ref symbol ^
    }
}

Upvotes: 0

AnT stands with Russia
AnT stands with Russia

Reputation: 320361

Non-qualified friend declarations always refer to members of the smallest enclosing namespace. When a class is declared in namespace usr, any non-qualified friend declaration inside that class refers to members of usr. I.e. your friend declaration declared usr::operator << as a friend.

The global ::operator << remains a non-friend in this case, which is why you get the error when you attempt to access private members of usr::A from ::operator <<.

If you want this to work, you have to either

  1. Make your operator << a member of usr, or

  2. Make sure the friend declaration explicitly refers to global operator << by using a qualified name ::operator << (this will also require introducing ::operator << before trying to refer to it by its qualified name).

I would suggest the first approach. If your class A is a member of namespace usr, then it is a good idea to declare all functions that handle A as members of usr as well. This will help you to avoid many problems with argument-dependent lookup (ADL) down the road.


But if for some reason you need your operator << to remain a member of global namespace, then here are the hoops you'd have to jump through to make it compile (combined into a single translation unit)

// Pre-declare `usr::A` to enable pre-declaration of our `::operator <<`
namespace usr {
    class A;
}

// Pre-declare our `::operator <<`
std::ostream& operator<<(std::ostream& os, const usr::A& a);

namespace usr {
    class A {
        private:
            int m_x;
        public:
            A(int x);
            friend std::ostream& ::operator<<(std::ostream& os, const usr::A& a);
            // Friend declaration uses qualified name - it explicitly 
            // refers to `::operator <<` declared above
    };
}

usr::A::A(int x) : m_x(x) {}

std::ostream& operator<<(std::ostream& os, const usr::A& a) {
    os << a.m_x;
    return os;
}

int main() {
    usr::A a(4);
    std::cout << a << std::endl;
}

Upvotes: 10

Related Questions