C++待决名

模板类模板函数模板)定义中,某些构造的含义可以在不同的实例化间有所不同。特别是,类型和表达式可能会取决于类型模板形参的类型和非类型模板形参的值。

template<typename T>
struct X : B<T> // "B<T>" 取决于 T
{
    typename T::A* pa; // "T::A" 取决于 T
                       // (此 "typename" 的使用的目的见下文)
    void f(B<T>* pb)
    {
        static int i = B<T>::i; // "B<T>::i" 取决于 T
        pb->j++; // "pb->j" 取决于 T
    }
};

待决名和非待决名的名字查找和绑定有所不同。

绑定规则

非待决名在模板定义点查找并绑定。即使在模板实例化点有更好的匹配,也保持此绑定:

#include <iostream>
 
void g(double) { std::cout << "g(double)\n"; }
 
template<class T>
struct S
{
    void f() const
    {
        g(1); // "g" 是非待决名,现在绑定
    }
};
 
void g(int) { std::cout << "g(int)\n"; }
 
int main()
{
    g(1);  // 调用 g(int)
 
    S<int> s;
    s.f(); // 调用 g(double)
}

如果非待决名的含义在定义点和模板特化的实例化点间有所改变,那么程序非良构,不要求诊断。在下列情形可能会出现这种情况:

  • 非待决名用到的类型在定义点不完整但在实例化点完整
  • 模板定义中对名字的查找找到 using 声明,但实例化中对应作用域找不到任何声明,因为该 using 声明是包展开而对应的包为空
(C++17 起)
  • 实例化使用了在定义点尚未定义的默认实参或默认模板实参
  • 实例化点的某个常量表达式使用了整型或无作用域枚举类型的 const 对象的值、constexpr 对象的值、引用的值或者 constexpr 函数的定义 (C++11 起),而该对象/引用/函数 (C++11 起)在模板的定义点还没有定义
  • 该模板在实例化点使用了非待决的类模板特化或变量模板特化 (C++14 起),而它所用的这个模板,或者从某个在定义点处还没有定义的部分特化所实例化,或者指名了某个在定义点处还没有声明的显式特化。

待决名的绑定则延迟到查找发生时。

ADL查找规则等我们不再介绍

待决类型

下列类型是待决类型:

  • 模板形参
  • 未知特化(见下文)的成员
  • 作为未知特化(见下文)的待决成员的嵌套类/枚举
  • 待决类型的 cv 限定版本
  • 从待决类型构成的复合类型
  • 元素类型待决或边界(如果存在)是值待决的数组类型
  • 形参包含一个或多个函数形参包的函数类型
(C++11 起)
  • 异常说明是值待决的函数类型
  • 模板名是某个模板形参,或者
  • 有任何类型待决,或值待决,或者是包展开 (C++11 起)的模板实参(即使用到的模板标识不带实参列表,如注入类名
  • 应用到类型待决表达式的 decltype 的结果

应用到类型待决表达式的 decltype 的结果是唯一的待决类型。两个这样的结果只有在它们的表达式等价时才指代同一类型。

(C++11 起)

注意;当前实例化的 typedef 成员只有在它指代的类型待决时才会是待决的。

类型待决表达式 

下列表达式是类型待决的:

  • 含有类型待决的子表达式的表达式
  • this,如果它的类是待决类型
  • 不是概念标识的 (C++20 起)标识表达式,且
  • 包含某个能被自身的名字查找找到至少一个待决声明的标识符
  • 包含待决的模板标识
  • 包含特殊标识符 __func__(如果某个外围函数是模板,类模板的非模板成员,或泛型 lambda (C++14 起))
(C++11 起)
  • 包含到某个待决类型的转换函数
  • 包含作为未知特化成员的嵌套名说明符或有限定标识
  • 指名当前实例化的某个待决成员,且该成员是“未知边界的数组”类型的静态数据成员
  • 包含某个能被自身的名字查找找到一个或更多的声明为具有返回类型推导的当前实例化的成员函数声明的标识符
(C++14 起)
  • 包含能被自身的名字查找找到一个初始化器为类型待决的结构化绑定声明的标识符
  • 包含能被自身的名字查找找到类型含占位符 auto 的非类型模板形参的标识符
  • 包含能被自身的名字查找找到以包含占位符类型的类型声明且初始化器是类型待决的变量(例如 auto 静态数据成员)的标识符,
(C++17 起)
  • 任何到待决类型的转型表达式
  • 创建待决类型对象的 new 表达式
  • 指代当前实例化的某个类型待决的成员的成员访问表达式
  • 指代未知特化的某个成员的成员访问表达式
(C++17 起)

注意:字面量、伪析构函数调用、alignofnoexcept (C++11 起)、sizeoftypeiddelete、和 throw 表达式始终不是类型待决的,因为这些表达式的类型不可能待决。

 值待决表达式

(C++20 起)
  • 为类型待决的
  • 为某个非类型模板形参的名字
  • 指名某个作为当前实例化的待决成员的静态数据成员,且未被初始化。
  • 指名某个作为当前实例化的待决成员的静态成员函数
  • 为具有整数或枚举 (C++11 前)字面 (C++11 起)类型的常量,并从值待决表达式初始化
  • alignofnoexcept、 (C++11 起)sizeoftypeid 表达式,其实参是类型待决表达式或待决的类型标识
  • 任何向待决类型转换或从值待决表达式转换的转型表达式
  • 取址表达式,其实参是指名某个当前实例化的待决成员的有限定标识
  • 取址表达式,其实参是求值为核心常量表达式的,指代某个作为具有静态或线程 (C++11 起)存储期的对象或成员函数的模板化实体
(C++17 起)

 可以理解成只有实例化(单态化)后才能确定类型和/或求值方法的表达式。

​​​​​​​

 待决名的 typename 消歧义符

在模板(包括别名模版)的声明或定义中,不是当前实例化的成员且取决于某个模板形参的名字不会被认为是类型,除非使用关键词 typename 或它已经被设立为类型名(例如用 typedef 声明或通过用作基类名)。

#include <iostream>
#include <vector>
 
int p = 1;
 
template<typename T>
void foo(const std::vector<T> &v)
{
    // std::vector<T>::const_iterator 是待决名,
    typename std::vector<T>::const_iterator it = v.begin();
 
    // 下列内容因为没有 'typename' 而会被解析成
    // 类型待决的成员变量 'const_iterator' 和某变量 'p' 的乘法。
    // 因为在此处有一个可见的全局 'p',所以此模板定义能编译。
    std::vector<T>::const_iterator* p; 
 
    typedef typename std::vector<T>::const_iterator iter_t;
    iter_t * p2; // iter_t 是待决名,但已知它是类型名
}
 
template<typename T>
struct S
{
    typedef int value_t; // 当前实例化的成员
 
    void f()
    {
        S<T>::value_t n{}; // S<T> 待决,但不需要 'typename'
        std::cout << n << '\n';
    }
};
 
int main()
{
    std::vector<int> v;
    foo(v); // 模板实例化失败:类型 std::vector<int> 中没有
            // 名字是 'const_iterator' 的成员变量
    S<int>().f();
}

关键词 typename 只能以这种方式用于限定名(例如 T::x)之前,但这些名字不必待决。

对前附 typename 的标识符使用通常的有限定名字查找。这与用详述类型说明符的情况不同,不管限定符如何都不改变查找规则:

struct A // A 拥有嵌套变量 X 和嵌套类型 struct X
{
    struct X {};
    int X;
};
 
struct B
{
    struct X {}; // B 拥有嵌套类型 struct X
};
 
template<class T>
void f(T t)
{
    typename T::X x;
}
 
void foo()
{
    A a;
    B b;
    f(b); // OK:实例化 f<B>,T::X 指代 B::X
    f(a); // 错误:不能实例化 f<A>:因为 A::X 的有限定名字查找找到了数据成员
}

待决名的 template 消歧义符

与此相似,模板定义中不是当前实例化的成员的待决名同样不被认为是模板名,除非使用消歧义关键词 template,或它已被设立为模板名:

template<typename T>
struct S
{
    template<typename U>
    void foo() {}
};
 
template<typename T>
void bar()
{
    S<T> s;
    s.foo<T>();          // 错误:< 被解析为小于运算符(但是MSVC可以正确解析,不符合标准)
    s.template foo<T>(); // OK
}

关键词 template 只能以这种方式用于运算符 ::(作用域解析)、->(通过指针的成员访问)和 .(成员访问)之后,下列表达式都是合法示例:

  • T::template foo<X>();
  • s.template foo<X>();
  • this->template foo<X>();
  • typename T::template iterator<int>::value_type v;

与 typename 的情况一样,即使名字并非待决或它的使用并未在模板的作用域中出现,也允许使用 template 前缀。

即使 :: 左侧的名字指代命名空间,也允许使用 template 消歧义符:

template<typename>
struct S {};
 
::template S<void> q; // 允许,但不需要

根据无限定名字查找针对成员访问表达式中的模板名的特殊规则,当非待决的模板名在成员访问表达式中出现时(-> 或 . 后),如果通过表达式语境中的常规名字查找找到了的具有相同名字的类或别名 (C++11 起)模板,那么就不需要消歧义符。然而,如果表达式语境中的查找所找到的模板与类语境中所找到的不同,那么程序非良构。 (C++11 前)

template<int>
struct A { int value; };
 
template<class T>
void f(T t)
{
    t.A<0>::value; // A 的常规查找找到类模板。A<0>::value 指名类 A<0> 的成员
    // t.A < 0;    // 错误:'<' 被当做模板实参列表的起始
}

 总结:

以上全部代码和解释均来自cppreference,稍微有一些省略

待决名 - cppreference.comhttps://zh.cppreference.com/w/cpp/language/dependent_name

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值