Можно ли использовать введенное имя класса в качестве имени типа в объявлении друга?

Рассмотрим этот код:

template <typename T>
class Singleton
{
};

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

Он компилируется в gcc и clang, но действителен ли он? [temp.local].1 говорит:

Когда он используется со списком аргументов-шаблона, в качестве аргумента-шаблона для параметра-шаблона шаблона или в качестве конечного идентификатора в уточненном спецификаторе-типа объявления шаблона дружественного класса, это имя-шаблона, которое относится к самому шаблону класса.

Часть, выделенная жирным шрифтом, кажется применимой, а для объявления друга требуется имя типа, а не имя шаблона (см. [class.friend]).

Компиляторы ошибаются или я неправильно читаю стандарт?


person Nikolai    schedule 25.01.2021    source источник
comment
Я думаю, что это должно быть friend class Singleton<Logger>;, чтобы позволить конкретному экземпляру базового класса шаблона быть другом, а не всем возможным экземплярам шаблона.   -  person Remy Lebeau    schedule 25.01.2021
comment
@RemyLebeau - Подружиться с всеми экземплярами будет template <class L> friend class Singleton<L>;, а не то, что в OP.   -  person StoryTeller - Unslander Monica    schedule 25.01.2021
comment
[temp.local] (как подраздел [temp.res]) в первую очередь относится к именам, которые используются внутри определений template. Действительно, если кто-то немного изменит ваш пример, GCC немедленно отклонит код. Я не уверен, применим ли этот раздел в равной степени для имени введенного класса при использовании в не-шаблонах.   -  person StoryTeller - Unslander Monica    schedule 25.01.2021
comment
@StoryTeller-UnslanderМоника ваш пример попадает на [temp.dep]/3. Это справедливо даже для более простого.   -  person Amir Kirsh    schedule 25.01.2021
comment
Часть, выделенная жирным шрифтом, неприменима, потому что имя находится не в объявлении шаблона дружественного класса (например, template <class> friend class Singleton;), а просто в объявлении дружественного класса.   -  person aschepler    schedule 26.01.2021
comment
@aschepler, пожалуйста, опубликуйте это как ответ, чтобы я мог его принять.   -  person Nikolai    schedule 26.01.2021


Ответы (2)


Когда он используется со списком аргументов-шаблона, в качестве аргумента-шаблона для параметра-шаблона шаблона или в качестве конечного идентификатора в уточненном спецификаторе-типа объявления шаблона дружественного класса, это имя-шаблона, которое относится к самому шаблону класса.

Условие, выделенное полужирным шрифтом, не относится к этому примеру, поскольку имя появляется в объявлении дружественного класса, но не в объявлении шаблона дружественного класса.

Аналогичный код, где применяется часть, выделенная жирным шрифтом:

template <typename T>
class Singleton
{
};

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

Объявление шаблона класса друга повторно объявляет шаблон класса Singleton и делает его другом. Тот же синтаксис допустим и в качестве первого объявления шаблона класса (см. пример в [temp.friend]/1.4, где шаблон класса frd объявлен и добавлен в друзья), но первое объявление не может быть экземпляром внедренного имени класса.

person aschepler    schedule 26.01.2021

Все примеры в [temp.local], использующие наследование, используют шаблонный производный класс, поэтому необходимо получить доступ к Базе с квалифицированным именем, т.е. через производный, как в [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>
};

Это делается для того, чтобы обойти правила поиска зависимых имен.

В этом разделе спецификаций нет примера для не шаблонного производного, но если производный не является шаблонным, следующее также должно работать:

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

Это интерпретируется как:

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

Что интерпретируется как:

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

В нашем случае:

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

То же, что:

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

Что то же самое, что:

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

Следует отметить, что определение в спецификации injected -имя-класса относится к:

Имя класса также привязано к области действия самого класса (шаблона); это известно как внедренное имя класса.

Я бы воспринял тот факт, что слово template появляется в круглых скобках, как намек спецификации на то, что внедренное-имя-класса может появиться и в классе без шаблона. И на самом деле он используется в другом месте спецификации, в контексте, отличном от шаблона, например здесь и здесь< /а>.

Чтобы закрыть это, спецификация добавляет в [dcl.type.simple ]#примечание-1:

Введенное имя класса никогда не интерпретируется как имя шаблона в контекстах, где будет выполняться вывод аргумента шаблона класса ([temp.local]).

Итак, я бы сказал, что ваш код соответствует спецификациям.


Обратите внимание, что [temp.local]#example-1 относится к случаи, когда компилятор не увидит class Y как Y<int>, а скорее как ::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
  };
};

Последний пример работает и в нашем случае, для объявления всех типов Singleton дружественными:

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

Однако вышеприведенное не компилируется в GCC из-за появляющейся старой ошибки в GCC.

Чтобы преодолеть ошибку GCC, можно использовать более подробный вариант:

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

Код для игры: https://godbolt.org/z/Mcez17

person Amir Kirsh    schedule 25.01.2021
comment
Этот ответ не относится к цитируемому предложению в [temp.local]. - person Nikolai; 25.01.2021
comment
Это относится к объявлению шаблона дружественного класса, где ваш случай является объявлением дружественного класса. - person Amir Kirsh; 26.01.2021