当前位置: 首页 > news >正文

Effective C++ 条款42:了解 typename 的双重含义

Effective C++ 条款42:了解typename的双重含义

核心思想在模板声明中,typenameclass可互换使用,但在模板内部,typename必须用于显式指明嵌套从属类型名称(nested dependent type name),以避免编译器解析歧义。对于非从属名称或基类成员列表中的嵌套从属类型名称,不得使用typename

⚠️ 1. typename的两种用途

用法对照

场景关键字示例说明
模板参数声明class/typenametemplate<class T>template<typename T>两者完全等价
嵌套从属类型名称前缀typenametypename T::const_iterator it;必须使用typename标识类型
基类列表中的名称class Derived: public Base<T>::Nested { ... }基类列表中不能使用typename
初始化列表中的名称Derived(int x) : Base<T>::Nested(x) { ... }成员初始化列表不能使用typename

代码示例

template<typename T>
class MyVector {
public:// 嵌套从属类型名称:必须使用typenametypedef typename T::iterator iterator; // 正确:typename声明iterator是类型// 错误:缺少typename导致编译错误// typedef T::const_iterator const_iterator;void print(const T& container) {// 嵌套从属类型名称:必须使用typenametypename T::const_iterator cit = container.begin(); // 正确// 非从属名称:不需要typenameint value = 42; // 非从属名称,直接使用}
};

🚨 2. typename的规则与例外

决策矩阵

场景是否使用typename原因示例
模板参数声明可选(class/typename)两者等价template<typename T>
嵌套从属类型名称前必须避免解析歧义typename T::iterator it;
基类列表中的嵌套类型禁止语法规定class Derived : Base<T>::Nested { ... }
成员初始化列表中的嵌套类型禁止语法规定Derived() : Base<T>::Nested() { ... }
非从属名称禁止不需要int value;
显式特化/实例化禁止不在模板定义中在特化中直接使用具体类型

错误使用案例

template<typename T>
class Widget {
public:// 错误:在基类列表中使用typename// class WidgetDerived : typename Base<T>::Nested { ... };// 错误:在初始化列表中使用typename// Widget() : typename Base<T>::Nested() { ... }// 错误:非从属名称使用typename// typename int value;
};

嵌套从属名称解析规则

template<typename T>
void process(const T& container) {// 假设T是一个容器类型,有const_iterator成员类型T::const_iterator it1 = container.begin(); // 可能被解析为静态成员变量(错误)typename T::const_iterator it2 = container.begin(); // 正确:明确为类型
}

⚖️ 3. 最佳实践与适用场景

场景1:标准容器迭代器

template<typename Container>
void printContainer(const Container& c) {// 必须使用typename标识嵌套从属类型typename Container::const_iterator it;for (it = c.begin(); it != c.end(); ++it) {std::cout << *it << ' ';}
}

场景2:模板元编程中的类型萃取

template<typename T>
struct TypeTraits {// 使用typename提取迭代器关联的类型typedef typename T::value_type value_type;typedef typename T::iterator_category iterator_category;
};// 使用
template<typename Iter>
void advance(Iter& it, int n) {// 使用typename获取类型特征typename TypeTraits<Iter>::iterator_category category;// ... 根据分类实现advance
}

现代C++增强

// C++11 using别名模板
template<typename T>
using RemoveReference = typename std::remove_reference<T>::type;// C++14起,标准库类型萃取有_v和_t版本,避免typename
template<typename T>
void func() {std::remove_reference_t<T> x; // 等价于typename std::remove_reference<T>::type
}

💡 关键设计原则

  1. 模板参数声明自由选择

    // class和typename在模板参数声明中完全等价
    template<class T> class A;
    template<typename T> class B;
    
  2. 嵌套从属类型必须加typename

    template<typename T>
    class Demo {
    public:// T::SubType 可能是类型或静态成员typename T::SubType member; // 必须加typename
    };
    
  3. 基类和初始化列表禁止加typename

    template<typename T>
    class Derived : public Base<T>::Nested { // 基类列表中不能加typename
    public:Derived(int x) : Base<T>::Nested(x) { ... } // 初始化列表中不能加
    };
    

依赖类型解析实战

template<typename Iter>
auto getValue(Iter it) -> typename std::iterator_traits<Iter>::value_type {return *it;
}// C++14起可用decltype(auto)简化
template<typename Iter>
decltype(auto) getValueSimplified(Iter it) {return *it;
}

模板元编程中的typename

// 检查T是否有名为type的嵌套类型
template<typename T, typename = void>
struct HasType : std::false_type {};template<typename T>
struct HasType<T, typename std::void_t<typename T::type>> : std::true_type {};// 使用
static_assert(HasType<std::underlying_type<int>>::value, "has type");

te

struct HasType<T, typename std::void_t> : std::true_type {};

// 使用
static_assert(HasType<std::underlying_type>::value, “has type”);

总结<:typename在C++模板编程中有双重角色。在声明模板参数时,它与class等价;在模板内部,它必须用于标识嵌套从属类型名称,以避免编译器将类型解释为静态成员。在基类列表和成员初始化列表中,即使出现嵌套从属类型名称,也不得使用typename。随着C++14引入_t_v类型萃取辅助,部分场景可避免显式使用typename,但在通用模板编程中仍需谨慎遵循规则。

http://www.xdnf.cn/news/1306891.html

相关文章:

  • AutoSar AP平台中EM,CM,SM,PHM,LT等AP基础软件都有宿主进程吗
  • Lecture 10: Concurrency 3
  • linux-数据链路层
  • C语言笔记6:C高级 part1
  • 【160页PPT】机械行业数字化生产供应链产品解决方案(附下载方式)
  • 深入理解Transformer:从训练机制到长文本处理的核心问题
  • GoLand深度解析:智能开发利器与cpolar内网穿透的协同革命
  • Linux系统编程—Linux基础指令
  • Point-LIO技术文档中文翻译解析
  • Python爬取推特(X)的各种数据
  • 活侠传 送修改器 免安装中文版
  • 深入理解 Python 闭包:从原理到实践
  • UE UDP通信
  • 小白挑战一周上架元服务——装饰器
  • 【C++】缺省参数
  • Java调用bat执行python脚本
  • 基于多分类的工业异常声检测及应用
  • Redis 知识点与应用场景
  • Linux软件编程-进程(2)及线程(1)
  • AI加持下的智能路由监控:Amazon VPC Direct Connect实战指南
  • Python 数据可视化:柱状图/热力图绘制实例解析
  • mc paper 1.20.4
  • 【机器学习深度学习】生成式评测
  • 谈谈《More Effective C++》的条款30:代理类
  • 宋红康 JVM 笔记 Day02|JVM的架构模型、生命周期、发展历程
  • 命令模式C++
  • LPDDR5训练过程
  • 【模型评估中的BLEU、ROUGE、Bertscore、BERT分别什么意思?】
  • 洛谷 P2842 纸币问题 1 -普及-
  • 系统时钟配置