【问题标题】:Can an injected class name be used as a type name in a friend declaration?注入的类名可以用作友元声明中的类型名吗?
【发布时间】:2021-04-29 12:00:55
【问题描述】:

考虑这段代码:

template <typename T>
class Singleton
{
};

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

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

当它与模板参数列表一起使用时,作为模板模板参数的模板参数,或作为朋友类模板声明的详细类型说明符中的最终标识符,它是一个引用类模板本身的模板名称。

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

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

【问题讨论】:

  • 我认为需要friend class Singleton&lt;Logger&gt;; 才能允许模板基类的特定实例化成为朋友,而不是模板的所有可能实例化。跨度>
  • @RemyLebeau - 结交 all 实例化将是 template &lt;class L&gt; friend class Singleton&lt;L&gt;; - 不是 OP 中的内容。
  • [temp.local](作为 [temp.res] 的子部分)主要是关于在 模板定义。事实上,如果有人修改您的示例GCC 会立即拒绝该代码。我不确定该部分是否同样适用于非模板中使用的注入类名称。
  • @StoryTeller-UnslanderMonica your example 落在 [temp.dep]/3。即使a more simple one 也是如此。
  • 粗体部分不适用,因为名称不在“朋友类模板声明”(如template &lt;class&gt; friend class Singleton;)中,只是“朋友类声明”。

标签: c++ language-lawyer class-template friend-class injected-class-name


【解决方案1】:

[temp.local] 中使用继承的所有示例都使用模板派生类,因此需要使用限定名称访问 Base,即 通过派生类,如[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的定义是指:

类名也绑定在类(模板)本身的范围内;这被称为 injected-class-name

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

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

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

所以,我会说您的代码符合规范。


请注意,[temp.local]#example-1 指的是编译器不会将class Y 视为Y&lt;int&gt; 而是视为::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
};

但由于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

【讨论】:

  • 这个答案没有解决 [temp.local] 中引用的句子。
  • 它指的是朋友类模板声明,你的情况是朋友类声明。
【解决方案2】:

当它与模板参数列表一起使用时,作为模板模板参数的模板参数,或作为朋友类模板声明的详细类型说明符中的最终标识符,它是一个引用类模板本身的模板名称。

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

粗体部分适用的类似代码:

template <typename T>
class Singleton
{
};

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

friend 类模板声明重新声明类模板Singleton 并使其成为friend。与类模板的第一个声明相同的语法也是合法的(请参阅the example in [temp.friend]/1.4,其中类模板frd 被声明并成为朋友),但第一个声明不能是注入类名的实例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-31
    相关资源
    最近更新 更多