问题描述
考虑这个代码:
template <typename T>
class Singleton
{
};
class Logger : public Singleton<Logger> {
friend class Singleton;
};
它在 gcc 和 clang 中编译,但它有效吗? [temp.local].1 说:
当它与模板参数列表一起使用时,作为模板模板参数的模板参数,或作为朋友类模板声明的详细类型说明符中的最终标识符,它是指类模板本身的模板名称。
粗体部分似乎适用,并且朋友声明似乎需要类型名称而不是模板名称(参见 [class.friend])。
是编译器错误还是我误读了标准?
解决方法
[temp.local] 中所有使用继承的示例都使用模板派生类,因此需要使用限定名称访问 Base,即通过 Derived,如[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>
};
这是为了克服dependent name lookup rules。
在规范的这一部分中没有非模板派生的示例,但如果派生不是模板化的,则以下内容也应该适用:
// 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-class-name规范中的定义是指:
class-name 也绑定在类(模板)本身的范围内;这被称为注入类名称。
我认为 template
一词出现在括号中,作为规范的提示,injected-class-name 也可能出现在非模板化类中。事实上,它在规范的其他地方使用,在非模板上下文中,例如 here 和 here。
为了结束这一点,规范在 [dcl.type.simple]#note-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
};
};
最后一个例子也适用于我们的例子,用于声明所有类型的单身人士成为朋友:
class Logger : public Singleton<Logger> {
template<class> friend class Singleton; // refers to ::Singleton
};
然而,由于 old reappearing bug in GCC,上述内容无法在 GCC 中编译。
要克服 GCC 错误,可以使用更详细的选项:
class Logger : public Singleton<Logger> {
template<class> friend class ::Singleton; // OK with GCC and Clang
};
要玩的代码:https://godbolt.org/z/Mcez17
,当它与模板参数列表一起使用时,作为模板模板参数的模板参数,或作为朋友类模板声明的详细类型说明符中的最终标识符,它是指类模板本身的模板名称。
粗体条件不适用于示例,因为名称出现在友元类声明中,而不是友元类模板声明中。
粗体部分适用的类似代码:
template <typename T>
class Singleton
{
};
class Logger : public Singleton<Logger> {
template <typename> friend class Singleton;
};
友元类模板声明重新声明类模板 Singleton
并使其成为友元。相同的语法作为类模板的第一个声明也是合法的(参见 the example in [temp.friend]/1.4 其中类模板 frd
被声明和成为朋友),但第一个声明不能是注入的类名的实例.