注入类名

来自cppreference.com
< cpp‎ | language

注入类名是在类的作用域内该类自身的名字。

类模板中,注入类名能用作指代当前模板的模板名,或指代当前实例化的类名。

解释

在类作用域中,当前类的名字会被视为一个公开成员名:这被称为注入类名(injected-class-name)。该名字的声明点紧跟类定义的开花括号之后。

int X;
 
struct X
{
    void f()
    {
        X* p;   // OK:X 指代注入类名
        ::X* q; // 错误:名称查找找到变量名,它隐藏结构体名
    }
};

与其他成员类似,注入类名会被继承。在私有或受保护继承的场合,某个间接基类的注入类名在派生类中最后可能会无法访问。

struct A {};
struct B : private A {};
struct C : public B
{
    A* p;   // 错误:无法访问注入类名 A
    ::A* q; // OK:不使用注入类名
};

在类模板中

与其他类相似,类模板也拥有注入类名。它的注入类名可以被用作模板名或类型名。

下列情况下,注入类名被当做类模板自身的模板名:

否则,它被当做类型名,并等价于模板名后随环绕于 <> 中的该类模板的各个模板形参。

template<template<class, class> class>
struct A;
 
template<class T1, class T2>
struct X
{
    X<T1, T2>* p;   // OK:X 被当做模板名
 
    using a = A<X>; // OK:X 被当做模板名
 
    template<class U1, class U2>
    friend class X; // OK:X 被当做模板名
 
    X* q;           // OK:X 被当做类型名,等价于 X<T1, T2>
};

在类模板特化部分特化的作用域内,当将注入类名用作类型名时,它等价于模板名后随环绕于 <> 中的该类模板特化或部分特化的各个模板实参。

template<>
struct X<void, void>
{
    X* p; // OK:X 被当做类型名,等价于 X<void, void>
 
    template<class, class>
    friend class X; // OK:X 被当做模板名(与在模板中相同)
 
    X<void, void>* q; // OK:X 被当做模板名
};
 
template<class T>
struct X<char, T>
{
    X* p, q; // OK:X 被当做类型名,等价于 X<char, T>
 
    using r = X<int, int>; // OK:可以用它指名另一特化
};

只要在作用域中,类模板或类模板特化的注入类名就能被用作模板名或类型名之一。

template<>
class X<int, char>
{
    class B
    {
        X a;            // 表示 X<int, char>
 
        template<class, class>
        friend class X; // 表示 ::X
    };
};
 
template<class T>
struct Base
{
    Base* p;
};
 
template<class T>
struct Derived: public Base<T*>
{
    typename Derived::Base* p; // Derived::Base 表示 Derived<T>::Base,即 Base<T*>
};
 
template<class T, template<class> class U = T::template Base>
struct Third {};
 
Third<Derived<int>> t; // OK:默认实参将注入类名用作模板

找到注入类名的查找在某些情况下会导致歧义(例如当在多于一个基类中找到它时)。如果所有找到的注入类名都指代同一类模板的特化,且该名字被用作模板名,那么注入类名指代类模板自身而非其特化,且没有歧义。

template<class T>
struct Base {};
 
template<class T>
struct Derived: Base<int>, Base<char>
{
    typename Derived::Base b;         // 错误:歧义
    typename Derived::Base<double> d; // OK
};

注入类名与构造函数

构造函数没有名字,但在构造函数的声明与定义中,外围类的注入类名被认为是构造函数的名称。

在有限定的名字 C::D 中,如果

  • 名字查找不忽略函数名<! cwg 1310 -->,且
  • D 在类 C 的作用域中的查找找到了注入类名

那么始终认为该限定名指名 C 的构造函数。这种名字只能用于构造函数的声明中(例如,在友元构造函数声明,构造函数模板特化,构造函数模板实例化,或构造函数定义中),或用于继承构造函数 (C++11 起)

struct A
{
    A();
    A(int);
 
    template<class T>
    A(T) {}
};
using A_alias = A;
 
A::A() {}
A_alias::A(int) {}
template A::A(double);
 
struct B : A
{
    using A_alias::A;
};
 
A::A a;         // 错误:A::A 被认为指名构造函数,而非类型
struct A::A a2; // OK:与 'A a2;' 相同
B::A b;         // OK:与 'A b;' 相同

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 1004 C++98 注入类名不能作为模板模板实参 允许,此时它指代类模板本身