8

Is this code invalid:

template <class T> struct A;

class C {
    template <class T> friend void A<T>::foo();
};

In GCC 6.1.0 it says:

error: member 'void A<T>::foo()' declared as friend before type 'A<T>' defined

     template <class T> friend void A<T>::foo();

Clang 3.8.0:

warning: dependent nested name specifier 'A<T>::' for friend class declaration 
is not supported; turning off access control for 'C' [-Wunsupported-friend]

And Visual Studio 2015 crashes:

fatal error C1001: An internal error has occurred in the compiler.
(compiler file 'f:\dd\vctools\compiler\cxxfe\sl\p1\c\template.cpp', line 8952)
        template <class T> friend void A<T>::foo();

More specifically, is A required to be defined before the friend declaration?

  template <class T> struct A;

  class C {
    static void foo();
    template <class T> friend void A<T>::f();
  };

  template <class T> struct A {
    void f() { }
  };

If so, why?

  • have a look at this http://en.cppreference.com/w/cpp/language/friend – piyushj May 09 '16 at 12:16
  • What clang tells you has nothing to do with forward declaration of `A`, try with a non templated class `A` and you get an error message similar to `g++`. clang message refers to *14.5.4/5 [temp.friend]*, which is not implemented, so clang simply turn off access control (try to call a private method of `C` from `main` and you'll understand). – Holt May 09 '16 at 12:57

4 Answers4

4

You refer to A's member function foo. This function is not known to exist yet, because you only forward declare A.

In other words, you'll have to declare A<T>::foo before C, as the GCC message tries to tell you (the other two are rather cryptic). This means you have to declare the complete interface of A before C, instead of only forward declaring it.

Oebele
  • 581
  • 1
  • 4
  • 17
  • 1
    The clang message has another meaning - If you make `A` non templated, you get the "correct" error message (*incomplete type `A` named in nested name specifier*). The problem for clang is that `A::` is a dependent name specifier and the message say that clang does not support friend access for it. – Holt May 09 '16 at 12:46
2

Solution

You can declare f as a friend of C in the following way:

class C;

template <class T> struct A {
  void f(C const &c);
};

class C {
  int i = 42;
public:
  static void foo();
  template <class T> friend void A<T>::f(C const &c);
};

template <class T> void A<T>::f(C const &c) { std::cout << c.i << std::endl; }

Live Demo

Justification

According to the standard §3.3.2/p6 Point of declaration [basic.scope.pdecl]:

After the point of declaration of a class member, the member name can be looked up in the scope of its class. [ Note: this is true even if the class is an incomplete class. For example,

struct X {
enum E { z = 16 };
int b[X::z]; // OK
};

— end note ]

The diagnostic is rather misleading both for GCC and CLANG. The real problem with your code is not the fact that you're trying to access the definition of an incomplete type, but rather is that you can only refer to a class member name only after it has been declared.

101010
  • 41,839
  • 11
  • 94
  • 168
1

The first problem is (I think) from §9.3/7 [class.mfct] and probably some other places in the standard (see clang message below and 101010's answer):

Previously declared member functions may be mentioned in friend declarations.

This problem is similar to the one from this question.

Since you did not declare A<T>::f before C, you cannot declare it has a friend of C.

But there is an hidden problem behing clang's message, if you make A a non-templated class, the message is different:

Incomplete type A in nested name specifier.

Which is closer to gcc message than the actual one, this is because clang's warning is about something else. Per §14.5.4/5 [temp.friend], the standard allows member of class template to be friend, so this must be valid:

template <typename T>
struct A {
    void f ();
};

class C {
    template <typename T>
    friend void A<T>::f(); // Ok, A<T>::f is already declared

    void private_member (); // See below
};

But clang still complains:

warning: dependent nested name specifier 'A::' for friend class declaration is not supported; turning off access control for 'C' [-Wunsupported-friend]

clang does not support such friend declaration, so it simply turns off access control for C, meaning that:

C c;
c.private_member();

Will be "valid" everywhere, which may not be what you want.

Community
  • 1
  • 1
Holt
  • 36,600
  • 7
  • 92
  • 139
0

Firstly, Forward declaration of a class is not sufficient if you need to use the actual class type, for example, if you need to use it as a base class, or if you need to use the methods of the class in a method.

Since here you try to use its details "foo()", there is no way compiler knows what is A::foo().. Compiler cannot distinguish if it is a typo (or) actual function.. it needs to know the declaration of A::foo() before you can use it.

If you still want to only to forward declare the class and make it a friend, see if friend classes fit your situation

template <class T> struct A;

class C{
   template <class T> friend struct A;
};
Sam Daniel
  • 1,800
  • 12
  • 22