Can an injected class name be used as a type name in a friend declaration?
Asked Answered
A

2

6

Consider this code:

template <typename T>
class Singleton
{
};

class Logger : public Singleton<Logger> {
    friend class Singleton;
};

It compiles in gcc and clang, but is it valid? [temp.local].1 says:

When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier of a friend class template declaration, it is a template-name that refers to the class template itself.

The bold part seems to apply, and a friend declaration seems to require a type name and not a template name (see [class.friend]).

Are the compilers wrong or am I misreading the standard?

Affirmative answered 25/1, 2021 at 16:42 Comment(6)
I think it would need to be friend class Singleton<Logger>; to allow a specific instantiation of the template base class to be a friend, not all possible instantiations of the template.Acrosstheboard
@RemyLebeau - Befriending all instantiations would be template <class L> friend class Singleton<L>; - not what's in the OP.Urbanna
[temp.local] (as a sub-section of [temp.res]) is primarily about names that are used inside template definitions. Indeed, if one modifies your example a bit GCC immediately reject the code. I'm uncertain if the section applies equally for the injected-class name when used in non-templates too.Urbanna
@StoryTeller-UnslanderMonica your example falls on [temp.dep]/3. This would be the case for even a more simple one.Salomone
The bold part does not apply because the name is not in a "friend class template declaration" (like template <class> friend class Singleton;), just a "friend class declaration".Siana
@StoryTeller-UnslanderMonica Befriending all instantiations would be template <class L> friend class Singleton<L>; [temp.friend]/7: Friend declarations shall not declare partial specializations.Obla
S
1

When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier of a friend class template declaration, it is a template-name that refers to the class template itself.

The condition in bold does not apply to the example because the name appears within a friend class declaration, but not a friend class template declaration.

Similar code where the part in bold does apply:

template <typename T>
class Singleton
{
};

class Logger : public Singleton<Logger> {
    template <typename> friend class Singleton;
};

The friend class template declaration redeclares the class template Singleton and makes it a friend. The same syntax is also legal as a first declaration of a class template (see the example in [temp.friend]/1.4 where class template frd is declared and befriended), but a first declaration can't be an instance of an injected class name.

Siana answered 26/1, 2021 at 17:37 Comment(0)
S
2

All examples in [temp.local] that use inheritance are using a templated Derived class, thus there is a need to access the Base with a qualified name, i.e. through the Derived, as in [temp.local]#example-2:

template <class T> struct Base {
  Base* p;
};

template <class T> struct Derived: public Base<T> {
    typename Derived::Base* p;   // meaning Derived​::​Base<T>
};

This is in order to overcome dependent name lookup rules.

There isn't an example for non-template Derived in this section of the specifications, but if the Derived is not templated, the following should work as well:

// same Base as above
struct Derived: public Base<int> {
    Base* p;   // meaning Derived​::​Base<int>
};

This is interpreted as:

struct Derived: public Base<int> {
    Derived::Base* p;
};

Which is interpreted as:

struct Derived: public Base<int> {
    Derived::Base<int>* p;
};

In our case:

class Logger : public Singleton<Logger> {
    friend class Singleton;
};

Is same as:

class Logger : public Singleton<Logger> {
    friend class Logger::Singleton;
};

Which is same as:

class Logger : public Singleton<Logger> {
    friend class Logger::Singleton<Logger>;
};

It is to be noted that the definition in the spec of injected-class-name refers to:

The class-name is also bound in the scope of the class (template) itself; this is known as the injected-class-name.

I would take the fact that the word template appears in parenthesis, as a hint by the spec that injected-class-name may appear also in a non-templated class. And in fact it is used elsewhere in the spec, in a non-template context, for example here and here.

To close this, the spec adds, at [dcl.type.simple]#note-1:

An injected-class-name is never interpreted as a template-name in contexts where class template argument deduction would be performed ([temp.local]).

So, I would say that your code is compliant with the specifications.


Note that [temp.local]#example-1 refers to cases where the compiler would not see class Y as Y<int> but rather as ::Y:

template<template<class> class T> class A { };
template<class T> class Y;
template<> class Y<int> {
  Y* p;                                 // meaning Y<int>
  Y<char>* q;                           // meaning Y<char>
  A<Y>* a;                              // meaning A<​::​Y>
  class B {
    template<class> friend class Y;     // meaning ​::​Y
  };
};

The last example works in our case as well, for declaring all types of Singleton befriended:

class Logger : public Singleton<Logger> {
    template<class> friend class Singleton; // refers to ::Singleton
};

Above however doesn't compile in GCC due to an old reappearing bug in GCC.

To overcome the GCC bug, one can use the more verbose option:

class Logger : public Singleton<Logger> {
    template<class> friend class ::Singleton; // OK with GCC and Clang
};

Code to play with: https://godbolt.org/z/Mcez17

Salomone answered 25/1, 2021 at 19:37 Comment(2)
This answer doesn't address the quoted sentence in [temp.local].Affirmative
It refers to a friend class template declaration where your case is a friend class declaration.Salomone
S
1

When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier of a friend class template declaration, it is a template-name that refers to the class template itself.

The condition in bold does not apply to the example because the name appears within a friend class declaration, but not a friend class template declaration.

Similar code where the part in bold does apply:

template <typename T>
class Singleton
{
};

class Logger : public Singleton<Logger> {
    template <typename> friend class Singleton;
};

The friend class template declaration redeclares the class template Singleton and makes it a friend. The same syntax is also legal as a first declaration of a class template (see the example in [temp.friend]/1.4 where class template frd is declared and befriended), but a first declaration can't be an instance of an injected class name.

Siana answered 26/1, 2021 at 17:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.