【C++ TypeName用法 】掌握C++中的TypeName:模板编程的瑞士军刀

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【C++ TypeName用法 】掌握C++中的TypeName:模板编程的瑞士军刀

1. 引言

1.1 为什么 typename 重要?

在 C++ 的世界里,类型是一切的核心。正如 Shakespeare 曾经说过,“名字中究竟有什么重要的?玫瑰,即使不叫这个名字,依然芬芳。” 在编程中,类型就像是这些“名字”,它们定义了数据如何存储,如何操作,以及如何与其他类型交互。

typename 就像是这个世界里的通行证,特别是在模板编程(Template Programming)中。它不仅帮助编译器理解复杂的类型关系,还使得代码更加灵活和可维护。

1.2 本文将要探讨的主要话题概览

本文将详细介绍 typename 的各种用途,包括但不限于模板参数、依赖类型(Dependent Types)、嵌套类型(Nested Types)以及与 std::conditional 等标准库的交互。我们将深入到底层源码,展示这些用法是如何影响代码生成和执行的。

1.2.1 你会从本文中获得什么?

  • 如何正确地使用 typename 关键字
  • 什么情况下应该使用 typename,什么情况下不应该
  • 如何利用 typename 编写更灵活、更健壮的代码

人们常说“知己知彼,百战不殆”。当你理解了 typename 的底层工作原理,你就能写出更高效、更安全的代码。

让我们通过一个简单的例子开始吧。想象一下你是一个木匠,你有各种各样的工具。但是,你是否会用锤子去拧螺丝呢?当然不会,你会用螺丝刀。同样地,typename 就是 C++ 工具箱里的一把精密螺丝刀。用对了地方,它能让一切变得简单。用错了地方,它可能会让事情变得一团糟。

template<typename T>
void function(T param) {
  // Do something
}

在上面的代码中,我们用 typename 定义了一个模板函数,它可以接受任何类型的参数。这就像是你拿起螺丝刀准备拧螺丝,你知道这把螺丝刀能适用于多种情况。


到这里,我们已经初步了解了 typename 的重要性和它在本文中的讨论范围。在接下来的章节里,我们将深入这个主题,探讨 typename 如何成为模板编程中不可或缺的工具。

希望你能通过本文,不仅仅是学到 typename 的各种用法,更是能理解其背后的原理和逻辑。如同心理学家 Carl Jung 曾经说过:“直到你使无意识变为意识,它将会控制你的生活并你将称之为命运。” 当你深入了解 typename 后,你将能更自由地掌控你的代码,而不是被它控制。

2. 基础:typename 在模板参数中的用法

在涉足 C++ 的模板编程世界之前,了解 typename 关键字的基础用法是至关重要的。它类似于一个通行证,准许你进入一个更加庞大和复杂的类型系统。在本章中,我们将聚焦于 typename 在模板参数列表中的基础用法。

2.1 typenameclass 的等价性

在 C++ 的模板编程中,typenameclass 两个关键字在模板参数列表里是可以互换的。这可能让初学者感到困惑:为什么有两个关键字做同样的事情?

template<typename T>
void foo(T t) {
    // ...
}
template<class T>
void bar(T t) {
    // ...
}

两者的存在主要是历史原因。早期的 C++ 标准中,class 被用于声明模板参数,后来为了消除语义上的困惑(因为这里并不一定是一个类类型),加入了 typename 作为更明确、更自解释的替代。

选择哪一个?

从技术角度看,这两者是完全等价的。但从代码可读性(Code Readability)和自文档化(Self-documenting)的角度来看,typename 更具优势。当你看到 typename,你会明确地知道后面跟随的是一个类型参数,而不是一个具体的类。

2.2 简单模板函数和 typename

在简单的模板函数中,typename 的作用就是用来声明一个或多个类型参数。这种情况下,它出现在模板参数列表里,并用于定义函数的通用性。

template<typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

在这个 max 函数模板中,typename T 指示这个函数可以用于任何类型 T,只要该类型支持 >: 运算符。

为什么需要通用性?

当你面对多种类型都需要进行相同操作的情况时,使用模板可以大大减少代码重复。像 “Don’t Repeat Yourself”(DRY)这样的原则在这里表现得尤为明显。

方法 优点 缺点
重载函数(Function Overloading) 易于实现 代码重复,维护性差
模板函数(Template Function) 代码复用,高度可定制 初次使用可能复杂,编译错误难以解读

正如俗话说,“一切都是权衡”(“It’s all about trade-offs”),选择最合适的方法取决于具体需求和上下文。

这样,你就了解了 typename 在模板参数列表中的基础应用,以及它在简化代码和增加代码复用性方面的威力。在接下来的章节中,我们将深入探讨更为复杂和高级的用法。这些高级用法不仅会打开一个全新的编程维度,还会让你在解决复杂问题时更加得心应手。

在我们跳入更为复杂的主题之前,花点时间熟悉和掌握这些基础是非常值得的。正如名言所说,“熟练掌握基础是成功的关键”(“Mastering the basics is the key to success”)。

3. 深入:依赖类型(Dependent Types)

3.1 什么是依赖类型(Dependent Types)?

在 C++ 模板编程中,依赖类型(Dependent Types)是指那些依赖于模板参数的类型。这种类型在模板实例化之前是不确定的,所以编译器不能预先知道这究竟是什么类型。这就像是你在一个新城市中寻找一家餐厅,除非你到达那里,否则你无法确定它是否存在。

template<typename T>
class MyClass {
public:
    typedef T::SubType SubType;  // SubType 是一个依赖类型
};

在这个例子中,T::SubType 是一个依赖类型,因为它依赖于模板参数 T

3.1.1 为什么依赖类型会引发问题

当面对依赖类型时,编译器会感到困惑。在 C++ 中,.:: 操作符可以用于多种目的:访问成员变量、成员函数,或者嵌套类型。由于这个多重用途,编译器在解析依赖类型时,无法确定一个名字(如 T::SubType)到底是一个类型还是其他东西。

这个问题在 C++ 早期版本中尤为突出,因为编译器需要在模板定义阶段解决这些歧义,而不是等到模板实例化时。这让编译器陷入了一种"先有鸡还是先有蛋"的困境。

3.2 为什么需要 typename 指定依赖类型?

“名字是所有事物的标签,是知识的代价。”这句话出自 Dale Carnegie 的《人性的弱点》。在编程中,为事物命名和确定其性质同样重要。这就是 typename 发挥作用的地方。

当你使用 typename,你实际上是在给编译器一个明确的指示:这个名字代表一个类型,不是其他任何东西。

template<typename T>
void myFunction() {
    typename T::SubType x;  // 使用 typename 明确指出 T::SubType 是一个类型
}

在这里,typename 解除了编译器的困惑,明确告诉它 T::SubType 是一个类型。

3.2.1 从底层看 typename 的作用

如果你翻开 C++ 标准库的源代码,你会发现 typename 被广泛地用于各种情境,特别是在类型推导和模板特化中。它是编译器如何解析复杂模板表达式的关键。

假设没有 typename,编译器在遇到像 T::SubType 这样的表达式时,会尝试查找该表达式在 T 中的定义。然而,由于 T 是一个模板参数,在模板被实例化之前,编译器无法知道 T::SubType 到底是什么。typename 实际上是一种“承诺”,即当模板实例化时,T::SubType 将是一个类型。

方法 是否需要 typename 适用情境
普通类型 明确的非依赖类型
模板参数 T 模板参数本身
依赖类型 依赖于模板参数的类型
嵌套类型 在类或结构体内部定义的依赖类型

3.3 代码示例:如何使用 typename

让我们通过一个简单的示例来展示 typename 的用法。

template<typename T>
class Container {
public:
    typedef typename T::value_type value_type;  // 使用 typename 指明 value_type 是一个类型
    // ...
};

在这个 Container 模板中,我们使用 typename 来指定 T::value_type 是一个类型。这样,当你使用像 std::vector 这样的类型作为 T 时,Container>::value_type 会正确地解析为 int

通过这种方式,我们能够让编译器理解和解析依赖类型,从而避免在模板编程中常见的类型推导错误。

4. typename 在嵌套类型中的用途

4.1 如何识别嵌套类型

在模板编程中,嵌套类型(Nested Types)是一个常见的现象。嵌套类型通常是某个类或模板类内部定义的类型。如果这个类本身就是一个模板类,那么这个嵌套类型就会成为一个所谓的“依赖类型”(Dependent Type)。

现实生活中,我们总是在寻找“依赖”的存在,无论是家庭、工作还是社交。在编程世界里,依赖类型就像是那些需要其他类型来定义或者完善自己的类型。

比如,考虑一个简单的 Container 模板类:

template<typename T>
class Container {
public:
    typedef T value_type;
    // ...
};

这里,value_type 是一个嵌套类型,它依赖于模板参数 T

4.1.1 明确嵌套类型的需要

在某些情况下,当你在使用模板类的嵌套类型时,你需要明确地告诉编译器这是一个类型。这就是 typename 发挥作用的地方。

为什么需要这么做呢?因为 C++ 的编译器是一种“悲观主义者”,它总是假设最坏的情况。在遇到模板代码时,编译器会假设它不知道任何非明确指定的类型。这就像是当你面临一个新的挑战或不确定性时,你可能会首先准备好面对最坏的结果。

template<typename T>
void foo() {
    T::value_type x;  // 错误!编译器不知道 T::value_type 是否为类型
}

在这个例子中,编译器不知道 T::value_type 是不是一个类型,除非你明确地告诉它。这就是 typename 被用于前置这种依赖类型的原因。

template<typename T>
void foo() {
    typename T::value_type x;  // 正确
}

4.2 typename 的作用和必要性

现在我们知道了什么是依赖类型以及如何识别它们。下一步是理解为什么我们需要 typename,以及在什么情况下使用它。

4.2.1 typename 的必要性

typename 的主要作用是消除歧义。没有它,编译器就无法确定特定名称是否代表一个类型。这就像在一个新城市里找路:除非有明确的指示,否则你可能会迷失方向。

Bjarne Stroustrup 在他的著作 “The C++ Programming Language” 中指出,类型是 C++ 世界中最基本的构建块之一。没有明确的类型信息,编译器就不能进行有效的类型检查,这会导致各种问题。

4.2.2 何时使用 typename

规则其实很简单:当你在处理依赖类型时,前置 typename 关键字。

情况 是否需要 typename 示例代码
非依赖类型 不需要 int x;
模板参数 不需要 template<typename T>
依赖类型 需要 typename T::value_type x;

这个表格清晰地展示了在哪些情况下需要使用 typename

4.3 代码示例

让我们通过一个实际的例子来更好地理解这一点。假设我们有一个模板函数,该函数接受一个容器作为参数,并返回该容器的第一个元素的类型。

template<typename Container>
auto firstElement(const Container& c) -> typename Container::value_type {
    return *c.begin();
}

注意这里的 typename Container::value_type。这是因为 Container::value_type 是一个依赖类型,所以我们需要使用 typename 关键字来明确这是一个类型。

这里我们也使用了尾返回类型(Trailing Return Type)语法,这是 C++11 引入的一个特性。这样做是为了明确返回类型是依赖于模板参数 Container 的。

5. typenamestd::conditional:编译时条件判断

5.1 std::conditional 简介

在 C++ 的模板元编程世界中,条件语句(if-else)在运行时的作用很清晰:根据某个条件选择不同的执行路径。但有时,我们希望在编译时做出这样的选择,特别是在类型选择方面。这时候,std::conditional 就派上了用场。

std::conditional 是一个模板类,接受三个模板参数:一个布尔表达式和两种类型。这个布尔表达式会在编译时求值,然后选择其中一种类型作为该模板类的嵌套类型(type)。

[

\text{std::conditional<condition, Type1, Type2>::type}

]

如果条件为 truetype 就是 Type1;否则,它就是 Type2

比如,你在写一个函数模板,需要根据输入类型是不是指针来选择不同的内部实现。你可能会这样写:

template<typename T>
void myFunction() {
    using TypeToUse = std::conditional_t<std::is_pointer_v<T>, int, double>;
    // ...
}

在这里,如果 T 是一个指针类型,TypeToUse 就会是 int;否则,它就会是 double

5.2 为什么在 std::conditional 中需要 typename

5.2.1 基础解释

让我们回到 std::conditional 的完整用法:

[

\text{typename std::conditional<condition, Type1, Type2>::type}

]

为什么我们需要在这里加上 typename?原因其实很简单:std::conditional<...>::type 是一个依赖类型(Dependent Type)。它依赖于前面给定的布尔表达式和两种类型,因此编译器不能在模板实例化前确定它是什么。

当人们面对多个选项时,他们通常需要明确的指引来做出决策。编译器也不例外:在面对模板和类型时,它需要明确的指示来确定某个名字是否代表一个类型。这就是 typename 发挥作用的地方。

5.2.2 深入底层:std::conditional 的源码解析

要更深入地了解为什么需要 typename,我们可以看一下 std::conditional 的简化版源码实现:

template<bool B, class T, class F>
struct conditional { typedef T type; };
template<class T, class F>
struct conditional<false, T, F> { typedef F type; };

这里,conditional 是一个模板,它定义了一个嵌套类型 type,该类型要么是 T,要么是 F,取决于第一个模板参数(布尔表达式)。

由于 type 是一个依赖于模板参数的类型,你必须使用 typename 来告诉编译器,std::conditional<...>::type 实际上是一个类型。

5.2.3 方法对比表

方法 优点 缺点 使用场景
if-else (运行时) 灵活,可以用在任何条件下 可能影响运行时性能 需要在运行时做出决策
std::conditional (编译时) 不影响运行时性能,类型安全 只能在编译时使用 需要在编译时根据类型或其他条件做出决策

从这个表格中,你可以明白为什么 std::conditionaltypename 在编译时类型选择中是如此重要的工具。

5.2.4 代码示例

让我们通过一个简单的例子来看看 std::conditionaltypename 是如何一起工作的:

#include <type_traits>
#include <iostream>
template<typename T>
void showType() {
    using TypeToUse = typename std::conditional<std::is_pointer_v<T>, int, double>::type;
    TypeToUse x = 42;
    std::cout << "Type: " << typeid(x).name() << ", Value: " << x << std::endl;
}
int main() {
    showType<int>();  // Type: double, Value: 42
    showType<int*>(); // Type: int, Value: 42
}

在这个例子中,showType() 函数使用 std::conditional 来选择一个类型(intdouble),这取决于模板参数 T 是否是一个指针。这里,typename 确保了编译器正确地识别了 std::conditional<...>::type 作为一个类型。

这样,通过一些深入但直观的方式,你现在应该对 std::conditionaltypename 在 C++ 模板编程中的角色有了更深入的了解。在处理复杂的类型逻辑时,它们是你的得力助手。毕竟,选择总是存在的——关键是如何做出明智的选择。

6. C++17 和以后:_v_t 的崛起

6.1 简化类型特性(Type Traits)的访问

C++17 引入了一个非常方便的特性,即变量模板(Variable Templates),用于简化类型特性(Type Traits)的使用。在 C++14 或更早的版本中,我们通常这样使用类型特性:

std::is_pointer<T>::value

或者对于依赖类型(Dependent Types):

typename std::remove_reference<T>::type

这样的写法,尽管明确,但稍显繁琐。人们通常更喜欢简短、直观的代码。这种倾向可能来自于我们的大脑结构,因为大脑总是试图找到解决问题的最简单、最直接的路径。

C++17 提供了一种更简洁的方式:

  • 使用 _v 后缀直接获取类型特性的值:
std::is_pointer_v<T>
  • 使用 _t 后缀直接获取嵌套类型:
std::remove_reference_t<T>

通过这种简化,代码变得更容易阅读和维护。

6.2 如何与 typename 结合使用

你可能会问,既然有了 _t_v,我们还需要 typename 吗?答案是:肯定需要。_t_v 是用于简化代码的工具,但 typename 仍然在处理依赖类型时是不可或缺的。

例如,在模板类或函数中,当你需要引用一个依赖于模板参数的嵌套类型,typename 仍然是必要的:

template<typename T>
void function() {
    typename SomeClass<T>::NestedType variable;
}

然而,当你使用 _t_v,通常不需要额外的 typename,因为它们本身就产生了一个非依赖类型。

6.3 代码示例:结合使用 _t, _vtypename

下面的代码示例展示了如何在一个模板函数中结合使用这几个元素:

#include <type_traits>
#include <iostream>
template<typename T>
void showTraits() {
    using NonRefType = std::remove_reference_t<T>;
    constexpr bool isPointer = std::is_pointer_v<NonRefType>;
    typename std::conditional<isPointer, int, double>::type variable = 42;
    std::cout << "Type: " << typeid(variable).name() << ", Value: " << variable << std::endl;
}
int main() {
    showTraits<int&>();  // Type: double, Value: 42
    showTraits<int*>();  // Type: int, Value: 42
}

在这个例子中,我们首先使用 std::remove_reference_t 来去除类型的引用(如果有的话)。然后,我们使用 std::is_pointer_v 来检查去除引用后的类型是否为指针。最后,我们使用 std::conditionaltypename 来选择一个类型(intdouble)。

这样,你不仅可以看到 _t_v 如何简化代码,还能理解 typename 在依赖类型上的作用。

7. 实用案例:typename 在实际代码中的应用

7.1 动态类型选择

在生活中,我们经常需要根据条件来做选择。同样,在编程世界里,我们也需要根据一些条件来选择适当的数据类型。在这方面,typename 与类型特性(Type Traits)结合使用,可以实现高度灵活的动态类型选择。

7.1.1 使用 std::conditional 选择类型

例如,让我们考虑一个情况,你需要一个容器来存储数据。如果数据量很小,使用 std::vector 是个不错的选择;但如果数据量很大,std::list 可能会更高效。

这里,你可以使用 std::conditional 配合 typename 来实现这一目标。

template <bool large_data>
using SuitableContainer = typename std::conditional<large_data, std::list<int>, std::vector<int>>::type;

在这个例子中,SuitableContainer 会解析为 std::list,而 SuitableContainer 则会解析为 std::vector

就像菜单上的多种选择一样,这里提供了一种机制让你根据情况选择最适合你的“菜品”。

7.1.2 搭配 std::enable_if 实现 SFINAE(Substitution Failure Is Not An Error)

SFINAE 是一种让编译器在模板实例化失败时选择另一种实现的机制。这里,typename 也经常用于配合 std::enable_if

template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
void foo(T value) {
    // 对整数型的处理
}
template <typename T, typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
void foo(T value) {
    // 对浮点型的处理
}

如果你传入一个整数,第一个 foo 函数会被调用;传入一个浮点数,第二个会被调用。这就像是在一个拥挤的房间里找到了两扇出口——无论你处于什么情况,总有一个适合你。

7.2 元编程和编译时优化

编译时的计算或操作通常被视为一种“黑魔法”,但实际上,这只是一种高效利用编译器的能力来提前完成计算的手段。typename 在这里扮演着至关重要的角色。

7.2.1 类型生成和操作

让我们来看一个元编程的简单例子:编译时的阶乘计算。

template <std::size_t N>
struct Factorial {
    static const std::size_t value = N * Factorial<N - 1>::value;
};
template <>
struct Factorial<0> {
    static const std::size_t value = 1;
};

这里并没有用到 typename,但如果我们要将这个阶乘计算嵌入到另一个模板中,并希望以类型的形式返回结果,typename 就派上用场了。

template <typename T, std::size_t N>
struct TypedFactorial {
    using type = T;
    static const T value = N * TypedFactorial<T, N - 1>::value;
};
template <typename T>
struct TypedFactorial<T, 0> {
    using type = T;
    static const T value = 1;
};

在这里,typename 确保了 T 是一个类型,这样 TypedFactorial 就可以接受任何类型 T

7.2.2 利用 typename 进行编译时分支

另一个常见用例是在编译时进行条件分支,以决定使用哪种类型或算法。这就好像在一条路的尽头选择转左还是转右,而这一决定会影响你之后的旅程。

template <typename T, bool isPolymorphic>
class Cloner {
public:
    using value_type = typename std::conditional<isPolymorphic, T*, std::unique_ptr<T>>::type;
};

在这里,value_type 将根据 isPolymorphic 的值来选择是使用裸指针还是 std::unique_ptr。这样,你就可以在一开始决定要走哪条路径,而不用在之后的代码中进行任何修改。

方法 应用场景 是否依赖于 typename 备注
std::conditional 类型选择 编译时条件分支
std::enable_if 函数重载 利用 SFINAE
类型特性(Type Traits) 类型信息查询 std::is_integral

在日常生活中,我们总是在不断地做选择,而在编程中也不例外。typename 为我们提供了一种优雅的方式来实现这些选择,使我们的代码更加灵活和可维护。就像 Winston Churchill 曾经说过的,“你将永远走向你自己选择的方向”,typename 赋予了我们在编程世界中做出明智选择的能力。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
89 10
|
23天前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
185 63
|
1月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
46 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
17天前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
|
17天前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
13 1
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
76 11
|
2月前
|
存储 算法 C++
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
文章详细探讨了C++中的泛型编程与STL技术,重点讲解了如何使用模板来创建通用的函数和类,以及模板在提高代码复用性和灵活性方面的作用。
48 2
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
|
1月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
51 5
|
29天前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
34 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
69 2