Why Does a C++ Friend Class Need a Forward Declaration Only in Other Namespaces

Why does a C++ friend class need a forward declaration only in other namespaces?

C++ Standard ISO/IEC 14882:2003(E)

7.3.1.2 Namespace member definitions

Paragraph 3

Every name first declared in a
namespace is a member of that
namespace
. If a friend declaration in
a non-local class first declares a
class or function
(this implies that the name of the class or function is unqualified) the friend class
or function is a member of the
innermost enclosing namespace.

// Assume f and g have not yet been defined.
void h(int);
template <class T> void f2(T);
namespace A {
class X {
friend void f(X); // A::f(X) is a friend
class Y {
friend void g(); // A::g is a friend
friend void h(int); // A::h is a friend
// ::h not considered
friend void f2<>(int); // ::f2<>(int) is a friend
};
};
// A::f, A::g and A::h are not visible here
X x;
void g() { f(x); } // definition of A::g
void f(X) { /* ... */} // definition of A::f
void h(int) { /* ... */ } // definition of A::h
// A::f, A::g and A::h are visible here and known to be friends
}

Your friend class BF; is a declaration of A::BF in namespace A rather than global namespace. You need the global prior declaration to avoid this new declaration.

How come forward declaration is not needed for friend class concept?

You're right, the friend declaration is kind of like a forward declaration.

The following compiles:

class A;
class B
{
friend A;
};

or

class B
{
friend class A;
};

this does not:

class B
{
friend A;
};

It's not actually the friend declaration that forward-declares class A, but the class keyword. That's why the second example doesn't work, because it doesn't know what A is. If you declare A beforehand, like in the first snippet, it can resolve A to a class declaration.

I stand corrected.

Declare a member-function of a forward-declared class as friend

As @Ben says, it's not possible, but you can give specific access just to that member function through a "passkey". It works a bit like the intermediate helper class, but is imho clearer:

// Storage.h
// forward declare the passkey
class StorageDataKey;

class Storage {
int data_;
public:
int data() { return data_; }
// only functions that can pass the key to this function have access
// and get the data as a reference
int& data(StorageDataKey const&){ return data_; }
};

// BigComplicatedClass.cpp
#include "BigComplicatedClass.h"
#include "Storage.h"

// define the passkey
class StorageDataKey{
StorageDataKey(){} // default ctor private
StorageDataKey(const StorageDataKey&){} // copy ctor private

// grant access to one method
friend void BigComplicatedClass::ModifyStorage();
};

void BigComplicatedClass::ModifyStorage(){
int& data = storage_.data(StorageDataKey());
// ...
}

a class-key must be declared when declaring a friend

I was surprised about this (and as a result deleted a previous incorrect answer). The C++03 standard says in 11.4:

An elaborated-type-specifier shall be used in a friend declaration for a class.

Then to make sure there's no misunderstanding, it footnotes that with:

The class-key of the elaborated-type-specifier is required.

GCC is the only compiler that I have that complains about the missing class-key, but it looks like other compilers are letting us get away with something non-standard...

Now as for the rationale - you'd have to ask someone who knows more about compilers (or standards) than I do.

Why doesn't the compiler like all the strings that are associated with Iter?

  1. You need a forward declaration class Iter; outside the declaration of class ContainerPerson. Otherwise the friend class is ContainerPerson::Iter which is unrelated to the Iter you declare later. See Why does a C++ friend class need a forward declaration only in other namespaces?

  2. Typo in the definition of Iter* ContainerPerson::CreateIterator(): should be createIterator, with lower case c.

  3. Typo in for(it->first; ...); should be it->first().

  4. Your iterations are mixed up. They should be

    for(it->first(); !it->isDoneEnd(); it->next()) {
// ...
}
for(it->end(); !it->isDoneBegin(); it->prev()) {
// ...
}

With these fixed it compiles and runs cleanly: https://godbolt.org/z/3K7T7d14h

Bonus bug for you to fix later:
You never delete the Iter which is allocated in ContainerPerson::createIterator(). That's a memory leak.

Forward declaration of nested types/classes in C++

You can't do it, it's a hole in the C++ language. You'll have to un-nest at least one of the nested classes.

Why do I need to forward declare class foo but not class bar, even though 'foo.h' and 'bar.h' are both included?

The problem is that you've a circular dependency with include files:

global.h includes level.h
level.h includes global.h

What happens is

  1. Compiler starts reading level.h
  2. Reads pragma once that marks the file as already included
  3. Finds inclusion of global.h and starts reading it (at that point)
  4. Reads pragma once that marks the file as already included
  5. Finds inclusion of level.h but ignores it (it was marked included in 2)
  6. Keeps on reading the rest of global.h but the class Level is unknown

If the project is not trivial you should make a diagram of your classes and modules and decides what depends on (is built on) what. This diagram should be a DAG (without loops). More specifically it should be possible to subdivide the modules in "layers" in which no module from a lower layer depends on a module in an higher layer.

If you have a loop in your dependency diagram the project will be much harder to deal with (e.g. unit testing) because your modules are not really modules but the whole program becomes just a huge ball of code.

Injection of a friend declaration into a namespace, Eckel, Vol1, pg:440?

C++11 Standard 7.3.1.2 (3) says:

"If a friend declaration in a nonlocal class first declares a class or function [footnote: this implies that the name of the class or function is unqualified] the friend class or function is a member of the innermost enclosing namespace."

So any unqualified friend function declaration introduces a free function in the innermost enclosing namespace; (probably) the book referes to this as to "injection into namespace".

Note that it is even possible to define (implement) a function inside friend declaration:

namespace Z
{
class C
{
friend void f(void){/*do something*/}
};
}

This friend declaration not only "injects" but also implements free function f in the namespace Z (f is not a member of class C itself!), so no other declarations or definitions are needed for Z::f.

As to your example,

friend void Y::func(Z::X*, int);

is qualified (prefixed with namespace/class name), so it doesn't declare a function, it only refers to a member function Y::func previously declared in class Y. Such friend declarations "injects" nothing.



Related Topics



Leave a reply



Submit