Bug 32204 - friend from global namespace in template class ignored
Summary: friend from global namespace in template class ignored
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.1.2
: P3 normal
Target Milestone: 4.6.0
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-06-04 12:01 UTC by klaus.kretschel
Modified: 2012-10-27 13:39 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work: 4.6.3, 4.7.0, 4.8.0
Known to fail: 4.4.2, 4.5.1
Last reconfirmed: 2009-12-08 23:11:05


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description klaus.kretschel 2007-06-04 12:01:22 UTC
First: I apologize if this report is missing something but I did not find enough information in the guidelines to be more accurate. Furthermore, I have only gcc 4.1.2 prerelease available (from Suse Linux 10.2), so I will be glad if somebody tells me the bug has been fixed in a later version.

The bug is related to 8355 and 9230, but I guess it's different.

The following program does not compile; depending on the kind of invocation of 'f' 3 different errors occur (where the messages of 1 and 3 contradict to each other, see below):

namespace A { template < class PIX > class B; }

void f(A::B<int>&);

template < class PIX > class A::B {
  friend void ::f(A::B<int>&);
  PIX z;
};

void f(A::B<int>& i) { int a = i.z; }

int main() {
  A::B<int> b;
  f(b);         // 1. error: f is ambiguous (no, there is no 2nd declaration!)
  ::f(b);       // 2. error: z is private (yes, but 'f' is a friend)
  A::f(b);      // 3. error: f is not a member of A (of course)
}

The only invocation that should yield an error is no. 3. No. 2 should definitely work, and I guess the double colon for the global namespace should be unnecessary anywhere.

No error occurs if I transfer 'f' from the global namespace to another one (C):

namespace A { template < class PIX > class B; }

namespace C { void f(A::B<int>&); }

template < class PIX > class A::B {
  friend void C::f(A::B<int>&);
  PIX z;
};

void C::f(A::B<int>& i) { int a = i.z; }

int main() {
  A::B<int> b;
  C::f(b);           // OK
  using namespace C;
  f(b);              // OK
}

Another work-around is to make 'f' an template function.
Comment 1 Jonathan Wakely 2009-12-08 23:11:05 UTC
Confirmed, the friend declaration appears to be declaring f in namespace A, despite the :: qualifier
Comment 2 jag-gnu 2011-01-17 05:37:59 UTC
Output of running a recent g++ on the code in comment 0 with cases 2 and 3 commented out:


bug.cpp: In function ‘void f(A::B<int>&)’:
bug.cpp:7:  error: ‘int A::B<int>::z’ is private
bug.cpp:10: error: within this context
bug.cpp: In function ‘int main()’:
bug.cpp:14: error: call of overloaded ‘f(A::B<int>&)’ is ambiguous
bug.cpp:10: note: candidates are: void f(A::B<int>&)
bug.cpp:6:  note:                 void A::f(A::B<int>&)


Which clearly shows the compiler thinks there is an A::f, even though case 3 says there isn't.

I suspect the code below has the same problem:

//namespace M {                                          // 1
  template <class T> T* makeT() { return new T; }
//}

namespace N {
  class Foo {
    template <class T> friend T* /*M*/::makeT();
    //friend Foo* ::makeT<>();                           // 2
    Foo() {}
  };
}

int main(void)
{
  /*M*/::makeT<N::Foo>();
}

bug.cpp: In function ‘T* makeT() [with T = N::Foo]’:
bug.cpp:14:   instantiated from here
bug.cpp:9: error: ‘N::Foo::Foo()’ is private
bug.cpp:2: error: within this context

Either 1) putting makeT in a class or namespace, or 2) specializing makeT on Foo works around the problem.
Comment 3 David Piepgrass 2012-03-26 21:20:11 UTC
I have run into this bug or a similar bug while porting my code from MSVC to GCC. I need to declare that a class in the global namespace is a friend of my template class. However, I discovered that the bug still occurs if no templates are involved. Here's my repro:

class Friend;
namespace Foo
{
	class Base {
	};
	class Derived : protected Base {
		friend class Friend;
	};
}
class Friend {
	void F(const Foo::Derived* data) const 
	{
		// error: 'Foo::Base' is an inaccessible base of 'Foo::Derived'
		const Foo::Base* dataB = data;
	}
};

The problem disappears if either (A) Derived is not in a namespace, or (B) Friend is in a namespace (the forward declaration of Friend is required and, of course, must be put in the namespace too).
Comment 4 David Piepgrass 2012-03-26 21:24:26 UTC
(In reply to comment #3) I forgot to mention my GCC version, 4.4.3 (the Windows build that comes with the current Android SDK.)
Comment 5 Jonathan Wakely 2012-03-26 23:45:55 UTC
(In reply to comment #3)
> I have run into this bug or a similar bug while porting my code from MSVC to
> GCC.

I don't think you have, I think GCC is right to reject your code and MSVC was wrong to accept it.

> I need to declare that a class in the global namespace is a friend of my
> template class. However, I discovered that the bug still occurs if no templates
> are involved.

Because you haven't hit this bug :)

> Here's my repro:
> 
> class Friend;
> namespace Foo
> {
>     class Base {
>     };
>     class Derived : protected Base {
>         friend class Friend;

The standard says this declares Friend as a member of the "innermost non-class scope" which is namespace Foo.

>     };
> }
> class Friend {
>     void F(const Foo::Derived* data) const 
>     {
>         // error: 'Foo::Base' is an inaccessible base of 'Foo::Derived'
>         const Foo::Base* dataB = data;
>     }
> };
> 
> The problem disappears if either (A) Derived is not in a namespace, 

Because then the innermost enclosing non-class scope is the global namespace, so the same ::Friend class is found.

> or (B)
> Friend is in a namespace (the forward declaration of Friend is required and, of
> course, must be put in the namespace too).

Because then you define the same class as declared by the friend declaration.


To fix your code you should change the friend declaration to refer to ::Friend

        friend class ::Friend;

Or in C++11

        friend Friend;

In both these forms the friend declaration refers to the already-declared ::Friend rather than declaring a new Foo::Friend class.
Comment 6 Jonathan Wakely 2012-03-26 23:51:26 UTC
G++ 4.6.0 and later correctly compile this

namespace A { template < class PIX > class B; }

void f(A::B<int>&);

template < class PIX > class A::B {
  friend void ::f(A::B<int>&);
  PIX z;
};

void f(A::B<int>& i) { int a = i.z; }

int main() {
  A::B<int> b;
  f(b);         // 1. error: f is ambiguous (no, there is no 2nd declaration!)
  ::f(b);       // 2. error: z is private (yes, but 'f' is a friend)
// A::f(b);      // 3. error: f is not a member of A (of course)
}

So I think this bug is fixed
Comment 7 etherice 2012-10-27 08:11:45 UTC
As Jonathan explains in comment #5, gcc is right to reject this code and MSVC is wrong to accept it. You will have to add a forward declaration of the class/function in order to declare it as a friend in the namespaced class. For example:

// on gcc 4.7.0 (linux), these forward declarations are required for the friend declarations in ns::NamespacedClass
class GlobalClass;
void globalFunction();

namespace ns {
	struct NamespacedClass {
		friend ::GlobalClass; // same result whether '::' or 'class' is part of declaration
		friend void ::globalFunction();
	private:
		NamespacedClass() {}
	};
}

struct GlobalClass {
	GlobalClass() { ns::NamespacedClass foo; }
};

void globalFunction() {
	ns::NamespacedClass foo;
}
Comment 8 etherice 2012-10-27 08:52:10 UTC
In MSVC's defense, the standard is vague (or insufficient) in this regard for 'friend class' declarations. It says:

"If a friend declaration appears in a local class (9.8) and the name specified is an unqualified name, a prior declaration is looked up without considering scopes that are outside the innermost enclosing non-class scope."
...
"For a friend class declaration, if there is no prior declaration, the class that is specified belongs to the innermost enclosing non-class scope, but if it is subsequently referenced, its name is not found by name lookup until a matching declaration is provided in the innermost enclosing nonclass scope."

The standard *should* specify whether the 'friend class declaration' case applies to qualified names. For example:

namespace ns {
  class NSClass {
    friend class ::SomeGlobalClass;
  };
}

Since ::SomeGlobalClass is qualified (via scope resolution operator) it explicitly belongs to the global namespace. However, the standard says that it shall "belong to the innermost enclosing non-class scope", which is a contradiction (or nonsense). This is why the standard *should* specify a case for qualified vs unqualified names in friend class declarations (as it does for normal friend declarations).

The assumption MSVC makes not only seems reasonable, but is also convenient for developers as it allows *hidden* forward declarations of names in outer namespaces. This avoids having to make an unnecessary explicit forward declaration.

Perhaps GCC should "interpret" this part of the standard similarly.
Comment 9 Jonathan Wakely 2012-10-27 13:13:13 UTC
(In reply to comment #8)
> In MSVC's defense, the standard is vague (or insufficient) in this regard for
> 'friend class' declarations. It says:
> 
> "If a friend declaration appears in a local class (9.8) and the name specified
> is an unqualified name, a prior declaration is looked up without considering
> scopes that are outside the innermost enclosing non-class scope."
> ...
> "For a friend class declaration, if there is no prior declaration, the class
> that is specified belongs to the innermost enclosing non-class scope, but if it
> is subsequently referenced, its name is not found by name lookup until a
> matching declaration is provided in the innermost enclosing nonclass scope."

That wording only applies to local classes, so is irrelevant here.  See 7.3.1.2 for the wording that covers non-local classes.

> The standard *should* specify whether the 'friend class declaration' case
> applies to qualified names. For example:

A friend class declaration using qualified name can't introduce a new name, it can only refer to an existing class, so there must be a prior declaration.  See footnote 95 from 7.3.1.2/3, which says that a friend declaration that first declares a class must be unqualified.

> namespace ns {
>   class NSClass {
>     friend class ::SomeGlobalClass;
>   };
> }
> 
> Since ::SomeGlobalClass is qualified (via scope resolution operator) it
> explicitly belongs to the global namespace.

Yes, but a prior declaration in the global namespace must exist.

> However, the standard says that it
> shall "belong to the innermost enclosing non-class scope", which is a
> contradiction (or nonsense).

No, you've misread the standard.

I think the standard covers your case, GCC behaves correctly here.

This bug report is for a different case anyway, this is not the right place to discuss it.
Comment 10 etherice 2012-10-27 13:39:05 UTC
(In reply to comment #9)

Jonathan- You're right on all counts. Thanks for clarifying (and apologies for getting a bit off-topic).