注入的类名可以用作友元声明中的类型名吗?

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

提问人:Nikolai 提问时间:1/26/2021 最后编辑:curiousguyNikolai 更新时间:1/27/2021 访问量:154

问:

请考虑以下代码:

template <typename T>
class Singleton
{
};

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

它以 gcc 和 clang 格式编译,但它有效吗?[temp.local].1说:

当它与 template-argument-list 一起使用时,作为模板 template-parameter 的 template-argument,或者作为友元类模板声明的 elaborated-type-specifier 中的最终标识符时,它是引用类模板本身的 template-name。

粗体部分似乎适用,并且友元声明似乎需要类型名称而不是模板名称(参见 [class.friend])。

是编译器错了还是我误读了标准?

C++ 语言律师 模板 friend-class 注入类名

评论

1赞 Remy Lebeau 1/26/2021
我认为需要允许模板基类的特定实例化成为朋友,而不是模板的所有可能实例化。friend class Singleton<Logger>;
2赞 StoryTeller - Unslander Monica 1/26/2021
@RemyLebeau - 与所有实例化交朋友将是 - 而不是 OP 中的内容。template <class L> friend class Singleton<L>;
1赞 StoryTeller - Unslander Monica 1/26/2021
[temp.local](作为 [temp.res] 的子部分)主要涉及模板定义中使用的名称。事实上,如果有人稍微修改了你的示例,GCC 会立即拒绝代码。我不确定该部分在非模板中使用时是否同样适用于注入类名称。
1赞 Amir Kirsh 1/26/2021
@StoryTeller-UnslanderMonica,你的例子落在 [temp.dep]/3 上。即使是更简单的情况也是如此。
2赞 aschepler 1/26/2021
粗体部分不适用,因为该名称不在“友类模板声明”(like )中,只是“友类声明”。template <class> friend class Singleton;

答:

2赞 Amir Kirsh 1/26/2021 #1

[temp.local] 中所有使用继承的示例都使用模板化的 Derived 类,因此需要使用限定名称访问 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>
};

这是为了克服依赖名称查找规则

规范的这一部分中没有非模板派生的示例,但如果派生未模板化,则以下内容也应该有效:

// 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 规范中的定义是指:

类名也绑定在类(模板)本身的作用域中;这称为 injected-class-name

我认为这个词出现在括号中这一事实,作为规范的暗示,injected-class-name 也可能出现在非模板化类中。事实上,它在规范的其他地方被使用,在非模板上下文中,例如 here 和 heretemplate

为了解决这个问题,规范在 [dcl.type.simple]#note-1 处添加了:

在执行类模板参数推导的上下文中,注入的类名永远不会被解释为模板名 ([temp.local])。

所以,我想说你的代码符合规范。


请注意,[temp.local]#example-1 是指编译器不会看到 as 而是看到的情况:class YY<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

评论

0赞 Nikolai 1/26/2021
此答案不涉及 [temp.local] 中引用的句子。
0赞 Amir Kirsh 1/26/2021
它指的是友元类模板声明,其中您的案例是友元声明。
1赞 aschepler 1/27/2021 #2

当它与 template-argument-list 一起使用时,作为模板 template-parameter 的 template-argument,或者作为友元类模板声明的 elaborated-type-specifier 中的最终标识符时,它是引用类模板本身的 template-name。

粗体条件不适用于该示例,因为该名称出现在友元类声明中,而不是友元类模板声明中。

类似的代码,其中粗体部分确实适用:

template <typename T>
class Singleton
{
};

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

友元类模板声明重新声明类模板并使其成为友元。作为类模板的第一个声明,相同的语法也是合法的(参见 [temp.friend]/1.4 中的示例,其中声明了类模板并成为好友),但第一个声明不能是注入的类名的实例。Singletonfrd