SFINAE

Substitution Failure Is Not An Error : 替换失败不是错误
*** 在模板的参数替换过程中,如果某个模板参数导致不合法的替换,编译器并不会立即报错,而是尝试寻找其他匹配的模板。 ***
当编译器在实例化一个模板时,进行模板参数替换,如果替换过程中发生了错误(例如类型不匹配、表达式非法等),编译器会忽略这个模板,而不会将其视为编译错误。
这时,编译器会继续寻找其他可能匹配的模板。如果没有找到其他匹配的模板,编译才会失败。
SFINAE 主要用于函数模板的重载,通过某些条件选择合适的模板函数,而忽略不适用的函数模板。

typename

template <typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
process(T value) {
std::cout << "Processing integral type: " << value << std::endl;
}

typename 关键字的作用是在模板中显式声明一个依赖于模板参数的类型。在这里,std::enable_if<std::is_integral::value, void>::type
是依赖于模板参数 T 的类型,因此需要使用 typename 来告诉编译器 type 是一个类型,而不是成员或其他符号。

模板

  1. 模板
  2. 模板全特化 : 全特化意味着你为某个具体类型完全重新定义模板的实现,而不是使用模板的默认实现。
    #include <iostream>
    using namespace std;

    // 普通函数模板
    template <typename T>
    T add(T a, T b) {
    return a + b;
    }

    // 函数模板的全特化:为 const char* 特化
    template <>
    const char* add(const char* a, const char* b) {
    return "This is a specialized version for const char*";
    }
  3. 模板偏特化 : 在某些类型的基础上进行部分特化,而不是完全特化。它允许对部分模板参数进行特定类型的处理,而不需要完全指定所有参数的类型。
    偏特化不能用于函数模板,只能用于类模板。偏特化常见的用法是在部分模板参数上进行特化处理。
    #include <iostream>
    using namespace std;

    // 定义一个类模板,接受两个类型参数
    template <typename T1, typename T2>
    class MyClass {
    public:
    MyClass() {
    cout << "Generic template version" << endl;
    }
    };

    // 对类模板进行偏特化:当第二个类型参数为 int 时
    template <typename T1>
    class MyClass<T1, int> {
    public:
    MyClass() {
    cout << "Partial specialization for T2 = int" << endl;
    }
    };
    // 对指针类型进行偏特化
    template <typename T>
    class MyClass<T*> {
    public:
    MyClass() {
    cout << "Partial specialization for pointers" << endl;
    }
    };

一个阶乘的简单例子

#include <iostream>

using namespace std;

template <int n> struct factorial {
static_assert(n >= 0, "Arg must be non-negative");
static const int value = n * factorial<n - 1>::value;
};

template <> struct factorial<0> { static const int value = 1; };

int main() {

printf("%d\n", factorial<10>::value);
return 0;
}

C++20 新特性

Concepts (约束)

在 C++20 之后,很多以前需要靠“偏特化”来区分类型的场景,现在可以用 Concepts 更优雅地解决。

// C++20 风格:不再需要写复杂的偏特化
template <typename T>
void process(T tail) {
if constexpr (std::is_pointer_v<T>) {
// 处理指针
} else {
// 处理普通值
}
}

放置模板定义时,通常会配合 Concepts。这让模板的声明变得极具可读性,且报错信息不再像以前那样是“天书”

#include <concepts>

// 使用 concept 约束模板参数
export template <std::integral T>
T add_integers(T a, T b) {
return a + b;
}

C++20 模块方式(Recommended)

优点:
*** 编译速度 ***:模块只需要编译一次。在传统头文件模式下,模板在每个包含它的 .cpp 中都要重新解析一遍。

*** 隔离性 ***:模块内部的辅助函数或宏不会污染外部代码。

*** 逻辑统一 ***:把模板实现写在“实现文件”里,只要这个文件是模块接口的一部分。

在模块中定义并导出
创建一个模块文件 MyTemplate.cppm (或 .ixx):

export module MyTemplate; // 声明模块名

export template <typename T>
class Calculator {
public:
T add(T a, T b) {
return a + b;
}

T multiply(T a, T b); // 也可以在这里只写声明
};

// 即使在同一个模块文件的后面写定义也是可以的
template <typename T>
T Calculator<T>::multiply(T a, T b) {
return a * b;
}

主程序中

import MyTemplate; // 直接导入,无需 #include

int main() {
Calculator<int> calc;
calc.add(1, 2);
return 0;
}