注入类名

来自cppreference.com
< cpp‎ | language

注入类名是所提及的类的作用域内的类名。

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

[编辑] 解释

类作用域中,当前类的名称被当做它如同是公开成员名;这被称为注入类名( injected-class-name )。名称的声明点立即后随类定义的开花括号。

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

类似其他成员,注入类名得到继承。在私有或受保护继承的场合,间接基类的注入类名可以在派生类中最后变得不可访问。

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<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 中,若

  • 名称查找不忽略函数名,且
  • 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;'