第十三章 : Names in Templates_《C++ Templates》notes

Names in Templates

      • 重难点
      • 多选题
      • 设计题


重难点

1. 名称分类与基本概念
知识点:

  • 限定名(Qualified Name):使用::.显式指定作用域的名称(如std::vector
  • 非限定名(Unqualified Name):不带作用域限定的名称(如vector
  • 依赖名(Dependent Name):依赖于模板参数的名称(如T::value_type
  • 非依赖名(Non-dependent Name):不依赖模板参数的名称(如int

测试用例:

#include <iostream>
#include <vector>template<typename T>
void foo() {T::value_type x;  // 依赖名(T未实例化前无法确定是否合法)
}int main() {foo<std::vector<int>>();  // 实例化时检查T::value_type合法性return 0;
}

2. 依赖名与非依赖名的查找规则
知识点:

  • 非限定名查找
    • 普通查找(Ordinary Lookup):在模板定义时查找所有可见的非依赖名
    • ADL(Argument-Dependent Lookup):在模板实例化时查找关联命名空间
  • 限定名查找
    • 直接在当前作用域链中查找,不触发ADL

示例代码:

namespace NS {struct S {};void bar(S) { std::cout << "NS::bar\n"; }
}template<typename T>
void baz(T t) {bar(t);  // 非限定名:普通查找+ADL
}int main() {NS::S s;baz(s);  // 调用NS::bar(通过ADL)return 0;
}

3. 注入类名(Injected Class Name)
知识点:

  • 类模板内部可以隐式使用类名作为模板名(无需<T>
  • 在派生类中可通过Base::Base访问基类模板

示例代码:

#include <iostream>// 基类模板 Base
template<typename T>
class Base {
public:// 定义一个类型别名 type,其类型为模板参数 Tusing type = T;
};// 派生类模板 Derived,继承自 Base<T>
template<typename T>
class Derived : public Base<T> {
public:// 派生类的构造函数Derived() {// 显式使用完整的基类模板实例化形式typename Base<T>::type x;// 为了让代码更完整,我们可以给 x 赋值并输出x = static_cast<T>(10);std::cout << "Value of x: " << x << std::endl;}
};int main() {// 实例化 Derived 类模板,模板参数为 intDerived<int> d;return 0;
}    

4. 友元模板(Friend Templates)
知识点:

  • 友元可以是函数模板、类模板或成员模板
  • 友元声明需在类外定义时显式指定模板参数

示例代码:

template<typename T>
class MyClass {friend void helper<>(MyClass<T>&);  // 友元函数模板
};template<typename T>
void helper(MyClass<T>& obj) {obj.value = 42;
}int main() {MyClass<int> obj;helper(obj);return 0;
}

5. 两阶段查找(Two-Phase Lookup)
知识点:

  • 第一阶段(定义时):检查非依赖名,忽略模板参数
  • 第二阶段(实例化时):检查依赖名,触发ADL

常见陷阱示例:

template<typename T>
void foo() {bar();  // 第一阶段查找bar,若未找到则报错
}namespace NS {void bar() {}
}int main() {foo<NS::Bar>();  // 错误!第一阶段未找到bar()return 0;
}

6. 代码测试与调试技巧
测试策略:

  1. 分阶段编译:先编译模板定义,再实例化观察错误
  2. 显式实例化:通过template class MyClass<int>;强制实例化
  3. 使用static_assert:在模板中添加静态断言验证条件

示例测试代码:

template<typename T>
class MyClass {static_assert(std::is_integral_v<T>, "T must be integral");
};int main() {MyClass<int> ok;       // 通过// MyClass<double> error;  // 编译失败(静态断言)return 0;
}

多选题

题目1
关于依赖名称的查找规则,以下哪些说法正确?
A. 非限定依赖名称在第二阶段通过ADL查找
B. 限定依赖名称直接在第一阶段查找
C. 成员模板中的依赖名称自动视为模板
D. typename关键字只能用于非限定依赖名称前

答案
A, C
详解
A正确:非限定依赖名称在第二阶段通过ADL查找
C正确:成员模板中的依赖名称需用template关键字显式指明

题目2
关于ADL的适用场景,以下哪些是正确的?
A. 查找非成员函数时参数类型所属的命名空间
B. 查找成员函数的基类链
C. 查找模板参数类型的嵌套类型
D. 查找全局作用域的函数

答案
A, C
详解
A正确:ADL通过参数类型所属命名空间查找函数
C正确:ADL会查找参数类型的嵌套类型

题目3
关于注入类名称,以下哪些描述正确?
A. 在类模板内部可直接使用未限定类名
B. 注入名称优先于外部同名函数
C. 可用于访问基类的成员
D. 实例化后指向具体模板实例

答案
A, D
详解
A正确:类模板内部可直接用MyClass代替MyClass<T>
D正确:注入名称在实例化后指向具体实例类型

题目4
关于名称查找阶段,以下哪些正确?
A. 非依赖名称仅在第一阶段查找
B. 依赖名称仅在第二阶段查找
C. 友元声明影响第二阶段查找
D. using声明影响第一阶段查找

答案
A, D
详解
A正确:非限定非依赖名称在第一阶段完成查找
D正确:using声明会向第一阶段作用域引入名称

题目5
关于模板参数作用域,以下哪些正确?
A. 模板参数作用域从声明处开始
B. 模板参数可隐藏外层作用域名称
C. 类模板参数作用域包含成员定义
D. 函数模板参数作用域包含默认实参

答案
A, B, C
详解
A正确:模板参数作用域起始于声明处
B正确:模板参数会隐藏外层同名名称
C正确:类模板参数作用域覆盖成员定义

题目6
关于友元声明的名称查找,以下哪些正确?
A. 友元函数声明影响普通名称查找
B. 友元类声明参与ADL查找
C. 显式友元模板影响第二阶段查找
D. 友元声明必须在使用前可见

答案
B, C
详解
B正确:友元类参与ADL查找路径
C正确:显式友元模板在第二阶段被考虑

题目7
关于using声明在模板中的作用,以下哪些正确?
A. 引入命名空间成员到模板作用域
B. 可用于解除名称隐藏
C. 必须在模板定义体外声明
D. 影响第一阶段名称查找

答案
A, B, D
详解
A正确:using可将命名空间成员引入当前作用域
B正确:可解除外层同名名称的隐藏
D正确:using声明影响第一阶段的普通查找

题目8
关于当前实例化的判断,以下哪些情况成立?
A. 直接使用未限定的类模板名
B. 访问成员模板的嵌套类型
C. 使用this->限定成员访问
D. 通过typename限定依赖类型

答案
A, B
详解
A正确:直接使用类模板名指向当前实例
B正确:成员模板的嵌套类型属于当前实例

题目9
关于两阶段查找的例外,以下哪些正确?
A. 非类型模板参数的默认实参在第二阶段处理
B. 虚函数表在第二阶段初始化
C. 默认成员初始化器在第一阶段处理
D. 异常规格不在两阶段处理范围内

答案
C, D
详解
C正确:默认成员初始化器在第一阶段处理
D正确:异常规格不属于两阶段处理范围

题目10
关于模板特化与名称查找的关系,以下哪些正确?
A. 显式特化不影响第一阶段查找
B. 偏特化参与第二阶段ADL查找
C. 全局特化优先于隐式实例化
D. 特化中的名称独立于主模板作用域

答案
A, C
详解
A正确:显式特化仅在实例化时被选择
C正确:显式全局特化优先于隐式实例化


设计题

题目1
设计一个模板类Logger,要求:

  • 支持日志级别(DEBUG/INFO/WARNING)
  • 使用ADL查找自定义日志处理器
  • 提供默认处理器输出到std::cout
  • 测试用例需验证ADL查找和默认行为

答案

#include <iostream>
#include <string>// 主模板
template<typename T>
class Logger {
public:void log(const std::string& msg) {handle_log(msg); // ADL查找自定义处理器}
};// 默认处理器(通过ADL查找)
void handle_log(const std::string& msg) {std::cout << "[DEFAULT] " << msg << std::endl;
}// 自定义处理器示例
namespace CustomLog {struct Handler {static void handle(const std::string& msg) {std::cerr << "[CUSTOM] " << msg << std::endl;}};// ADL辅助函数void handle_log(const std::string& msg) {Handler::handle(msg);}
}// 测试用例
int main() {Logger<int> logger1;logger1.log("Hello"); // 调用默认处理器Logger<CustomLog::Handler> logger2;logger2.log("World"); // 调用自定义处理器return 0;
}

题目2
实现一个依赖名称查找的智能指针模板,要求:

  • 支持自定义删除器
  • 删除器通过依赖名称查找
  • 默认删除器使用delete
  • 测试用例需验证自定义删除器和默认行为

答案

template<typename T, typename Deleter = void>
class SmartPtr {T* ptr;
public:SmartPtr(T* p) : ptr(p) {}~SmartPtr() {delete_ptr(ptr); // 依赖名称查找}private:// 依赖名称:通过ADL查找Deleter::delete_ptrvoid delete_ptr(T* p) {Deleter::delete_ptr(p);}
};// 默认删除器
struct DefaultDeleter {static void delete_ptr(void* p) {::delete static_cast<int*>(p);}
};// 自定义删除器
struct FileDeleter {static void delete_ptr(FILE* p) {fclose(p);}
};// 测试用例
int main() {SmartPtr<int> ptr1(new int(42)); // 使用默认删除器SmartPtr<FILE, FileDeleter> ptr2(fopen("test.txt", "w")); // 使用自定义删除器return 0;
}

题目3
设计一个支持注入类名称的模板元编程工具,要求:

  • 提供类型特征检测接口
  • 注入类名称简化成员访问
  • 测试用例验证注入名称和普通成员访问一致性

答案

template<typename T>
struct TypeTraits {// 注入类名称简化成员访问using value_type = typename T::value_type;using iterator = typename T::iterator;static constexpr bool has_size = requires(T t) {{ t.size() } -> std::convertible_to<std::size_t>;};
};// 测试容器
template<typename T>
struct MyContainer {using value_type = T;using iterator = T*;std::size_t size() const { return 0; }
};// 测试用例
int main() {static_assert(TypeTraits<MyContainer<int>>::has_size);static_assert(std::is_same_v<TypeTraits<MyContainer<int>>::value_type, int>);return 0;
}

题目4
实现一个支持当前实例化的模板偏特化检测器,要求:

  • 判断给定类型是否为当前实例化
  • 使用this->限定成员访问
  • 测试用例验证检测逻辑

答案

template<typename T>
struct IsCurrentInstantiation {static constexpr bool value = false;
};template<typename T>
struct MyClass {template<typename U>struct Inner {static constexpr bool is_current = std::is_same_v<U, MyClass<T>::Inner>; // 当前实例化检测};
};// 测试用例
int main() {MyClass<int>::Inner<double> inner;static_assert(inner.is_current);return 0;
}

题目5
设计一个结合ADL和模板参数作用域的工具,要求:

  • 自动注册类型到工厂类
  • 使用ADL查找注册函数
  • 测试用例验证多命名空间注册

答案

#include <map>
#include <string>// 类型注册工厂
template<typename Key>
class Registry {std::map<Key, std::string> registry;
public:template<typename T>void register_type(const std::string& name) {registry[name] = typeid(T).name(); // 依赖名称查找}void list_types() const {for (const auto& [name, type] : registry) {std::cout << name << " -> " << type << std::endl;}}
};// ADL注册函数
namespace NS1 {struct TypeA {};void register_type(Registry<TypeA>& reg, const std::string& name) {reg.register_type<TypeA>(name);}
}namespace NS2 {struct TypeB {};void register_type(Registry<TypeB>& reg, const std::string& name) {reg.register_type<TypeB>(name);}
}// 测试用例
int main() {Registry<TypeA> reg_a;NS1::register_type(reg_a, "TypeA");Registry<TypeB> reg_b;NS2::register_type(reg_b, "TypeB");reg_a.list_types();reg_b.list_types();return 0;
}

测试用例执行说明

  1. 编译命令(使用C++17标准):
    g++ -std=c++17 -o test test.cpp && ./test
    
  2. 预期输出
    • 题目1输出:
      [DEFAULT] Hello
      [CUSTOM] World
      
    • 题目2输出(无输出,程序正常退出)
    • 题目3输出(无输出,静态断言通过)
    • 题目4输出(无输出,静态断言通过)
    • 题目5输出:
      TypeA -> St4TypeA
      TypeB -> St4TypeB
      

设计要点说明

  1. ADL机制:通过自定义命名空间和辅助函数实现日志处理器的动态选择
  2. 依赖名称delete_ptr方法通过依赖名称查找实现自定义删除逻辑
  3. 注入类名称TypeTraits直接使用T::value_type简化成员访问
  4. 当前实例化检测:利用this->限定符实现模板偏特化的运行时检测
  5. 多命名空间注册:通过ADL在不同命名空间中注册类型到统一工厂

总结与学习路径

  1. 理解名称分类:区分依赖/非依赖名、限定/非限定名
  2. 掌握查找规则:普通查找 vs ADL,两阶段查找机制
  3. 实践友元与注入类名:通过代码示例熟悉语法
  4. 调试技巧:利用静态断言和分阶段编译定位问题

建议通过以下步骤巩固知识:

  1. 手动推导示例代码的名称查找过程
  2. 编写包含模板继承和友元的复杂案例
  3. 使用不同编译器(如GCC/Clang)观察错误信息差异

遇到具体问题时,可结合nm工具查看符号表,或使用-fdump-class-hierarchy等编译器选项分析模板实例化结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/74325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

整合vue+Element UI 开发管理系统

1、 安装 Node.js 和 npm 确保安装了 Node.js 和 npm。可以通过 Node.js 官网 下载。 2、 创建 Vue 项目 安装cli npm install -g vue/cli 使用 Vue CLI 创建一个新的 Vue 项目。 vue create admin-system cd admin-system npm run serve 出现这个页面表示vue创建成功 安…

3. 轴指令(omron 机器自动化控制器)——>MC_Stop

机器自动化控制器——第三章 轴指令 9 MC_Stop变量▶输入变量▶输出变量▶输入输出变量 功能说明▶指令详情▶时序图▶重启运动指令▶多重启动运动指令▶异常 MC_Stop 使轴减速停止。 指令名称FB/FUN图形表现ST表现MC_Stop强制停止FBMC_Stop_instance (Axis :《参数》 ,Execu…

C#中修饰符——abstract、virtual

一、多态简介 在面向对象编程的过程中&#xff0c;多态体现出来的是【一个接口&#xff0c;多个功能】&#xff1b;多态性体现在2个方面&#xff1a; 1、程序运行时&#xff0c;在方法参数、集合或数组等位置&#xff0c;派生类对象可以作为基类的对象处理&#xff1b;这样该对…

Spring Boot + Spring Integration整合MQTT打造双向通信客户端

1. 概述 本文分两个章节讲解MQTT相关的知识&#xff0c;第一部份主要讲解MQTT的原理和相关配置&#xff0c;第二个章节主要讲和Spring boot的integration相结合代码的具体实现&#xff0c;如果想快速实现功能&#xff0c;可直接跳过第一章节查看第二章讲。 1.1 MQTT搭建 为了…

2025前端面试题记录

vue项目目录的执行顺序是怎么样的&#xff1f; 1、package.json   在执行npm run dev时&#xff0c;会在当前目录寻找package.json文件&#xff0c;此文件包含了项目的名称版本、项目依赖等相关信息。 2、webpack.config.js(会被vue-cli脚手架隐藏) 3、vue.config.js   对…

专题|Python贝叶斯网络BN动态推理因果建模:MLE/Bayes、有向无环图DAG可视化分析呼吸疾病、汽车效能数据2实例合集

原文链接&#xff1a;https://tecdat.cn/?p41199 作为数据科学家&#xff0c;我们始终在探索能够有效处理复杂系统不确定性的建模工具。本专题合集系统性地解构了贝叶斯网络&#xff08;BN&#xff09;这一概率图模型在当代数据分析中的创新应用&#xff0c;通过开源工具bnlea…

WX小程序

下载 package com.sky.utils;import com.alibaba.fastjson.JSONObject; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.Cl…

Vulnhub-wordpress通关攻略

姿势一、后台修改模板拿WebShell 第一步&#xff1a;进⼊Vulhub靶场并执⾏以下命令开启靶场&#xff1b;在浏览器中访问并安装好.... 第二步&#xff1a;找到外观--编辑--404.php&#xff0c;将原内容删除并修改为一句话木马&#xff0c;点击更新--File edited successfully. &…

Spring Boot(十六):拦截器Interceptor

拦截器的简介 拦截器&#xff08;Interceptor&#xff09;​是Spring框架中的概​念&#xff0c;它同样适​用于Spring Boot&#xff0c;​因为Spring Boot是基于Spring框架的。拦截器是​一种AOP&#xff08;面向切面编程&#xff09;​的轻量级实现方式&#xff0c;它允许我…

Kotlin v2.1.20 发布,标准库又有哪些变化?

大家吼哇&#xff01;就在三小时前&#xff0c;Kotlin v2.1.20 发布了&#xff0c;更新的内容也已经在官网上更新&#xff1a;What’s new in Kotlin 2.1.20 。 我粗略地看了一下&#xff0c;下面为大家选出一些我比较感兴趣、且你可能也会感兴趣的内容。 注意&#xff01;这里…

开源链动2+1模式、AI智能名片与S2B2C商城小程序源码在社交电商渠道拓宽中的协同应用研究

摘要&#xff1a;本文基于"开源链动21模式""AI智能名片""S2B2C商城小程序源码"三大技术要素&#xff0c;探讨社交电商时代商家渠道拓宽的创新路径。通过解析各技术的核心机制与应用场景&#xff0c;结合京东便利店等实际案例&#xff0c;论证其对…

【蓝桥杯速成】| 10.回溯切割

前面两篇内容我们都是在做有关回溯问题的组合应用 今天的题目主题是&#xff1a;回溯法在切割问题的应用 题目一&#xff1a;分割回文串 问题描述 131. 分割回文串 - 力扣&#xff08;LeetCode&#xff09; 给你一个字符串 s&#xff0c;请你将 s 分割成一些 子串&#xff…

【嵌入式硬件】三款DCDC调试笔记

关于开关电源芯片&#xff0c;重点关注输入电源范围、输出电流、最低压降。 1.MP9943: 以MP9943为例&#xff0c;输入电压范围4-36V&#xff0c;输出最大电流3A&#xff0c;最低压降为0.3V 调整FB使正常输出为5.06V 给定6V空载、5V空载、5V带2A负载的情况&#xff1a; 6V带2A…

2025年03月18日柯莱特(外包宁德)一面前端面试

目录 自我介绍你怎么从0到1搭建项目的webpack 的构建流程手写webpack插件你有什么想问我的吗 2. 你怎么从 0 到 1 搭建项目的 在面试中回答从 0 到 1 搭建前端项目&#xff0c;可按以下详细步骤阐述&#xff1a; 1. 项目前期准备 需求理解与分析 和产品经理、客户等相关人…

在vitepress中使用vue组建,然后引入到markdown

在 VitePress 中&#xff0c;每个 Markdown 文件都被编译成 HTML&#xff0c;而且将其作为 Vue 单文件组件处理。这意味着可以在 Markdown 中使用任何 Vue 功能&#xff0c;包括动态模板、使用 Vue 组件或通过添加 <script> 标签为页面的 Vue 组件添加逻辑。 值得注意的…

Jupyter Notebook 常用命令(自用)

最近有点忘记了一些常见命令&#xff0c;这里就记录一下&#xff0c;懒得找了。 文章目录 一、文件操作命令1. %cd 工作目录2. %pwd 显示路径3. !ls 列出文件4. !cp 复制文件5. !mv 移动或重命名6. !rm 删除 二、代码调试1. %time 时间2. %timeit 平均时长3. %debug 调试4. %ru…

Java面试黄金宝典12

1. 什么是 Java 类加载机制 定义 Java 类加载机制是 Java 程序运行时的关键环节&#xff0c;其作用是把类的字节码文件&#xff08;.class 文件&#xff09;加载到 Java 虚拟机&#xff08;JVM&#xff09;中&#xff0c;并且将字节码文件转化为 JVM 能够识别的类对象。整个类…

第十四章:模板实例化_《C++ Templates》notes

模板实例化 核心知识点解析多选题设计题关键点总结 核心知识点解析 两阶段查找&#xff08;Two-Phase Lookup&#xff09; 原理&#xff1a; 模板在编译时分两个阶段处理&#xff1a; 第一阶段&#xff08;定义时&#xff09;&#xff1a;检查模板语法和非依赖名称&#xff0…

LSM-Tree(Log-Structured Merge-Tree)详解

1. 什么是 LSM-Tree? LSM-Tree(Log-Structured Merge-Tree)是一种 针对写优化的存储结构,广泛用于 NoSQL 数据库(如 LevelDB、RocksDB、HBase、Cassandra)等系统。 它的核心思想是: 写入时只追加写(Append-Only),将数据先写入内存缓冲区(MemTable)。内存数据满后…

LangChain组件Tools/Toolkits详解(6)——特殊类型注解Annotations

LangChain组件Tools/Toolkits详解(6)——特殊类型注解Annotations 本篇摘要14. LangChain组件Tools/Toolkits详解14.6 特殊类型注解Annotations14.6.1 特殊类型注解分类14.6.1 InjectedToolArg构建运行时绑定值工具14.6.3 查看并传入参数14.6.4 在运行时注入参数14.6.5 其它特…