文章目录
- 一、元编程的引入
- 二、顺序、分支、循环代码的编写方式
- 1.顺序代码的编写方式
- 2.分支代码的编写方式
- 3.循环代码的编写方式
 
- 三、减少实例化的技巧
这一章写的既浅又乱,为了知识的完整性先传上来,之后会重构
一、元编程的引入
泛型编程提供了一种方式来编写通用的、类型无关的代码。然而,当需要针对某些特殊情况进行优化或引入额外的处理逻辑时,泛型编程可能就不够用了。这时,元编程可以作为一个补充,允许开发者在编译时根据类型或其他条件生成或选择代码。例如,你可以定义一个模板,它在编译时检查类型是否满足某个特性(如是否是POD类型),然后根据这个特性来选择不同的实现。
-  元编程与编译期计算 下面解释了如何使用编译期运算来辅助运行期计算: -  编译期计算的优势 - 编译期计算可以在程序运行之前完成,这意味着不需要在程序执行时消耗计算资源。
- 编译期计算可以利用类型信息和模板参数来生成最优化的代码。
 
-  编译期与运行期的决策 - 需要仔细分析哪些计算可以在编译期完成,哪些必须在运行期进行。
- 编译期计算通常适用于那些不依赖于运行时数据的计算,如类型属性的计算、常量表达式的求值等。
 
-  编译期与运行期的结合 - 元编程不是简单地将计算一分为二,而是需要根据具体情况来决定哪些部分应该在编译期完成,哪些部分保留到运行期。
- 例如,如果一个算法的性能关键部分可以在编译期确定,那么这部分可以作为模板的一部分来实现。而对于依赖于运行时输入的部分,则需要在运行期处理。
 
-  运行期确定的信息 - 如果某种信息需要在运行期确定,比如用户输入或外部数据,那么这部分通常无法利用编译期计算。
- 但是,即使在这种情况下,也可以使用编译期计算来辅助运行期计算,比如通过模板元编程来生成运行期使用的代码或数据结构。
 
-  模板元编程示例 - 假设你有一个模板函数,它根据传入的类型参数来决定使用哪种算法。这种决策可以在编译期完成,而算法的具体实现则在运行期执行。
- 另一个例子是使用模板特化来实现类型特征的检测,如检查一个类型是否具有特定的成员函数或属性,然后在编译期根据这些特征生成相应的代码。
 
-  性能优化 - 通过将尽可能多的计算移至编译期,可以减少运行时的开销,提高程序的执行效率。
- 同时,编译期生成的代码可以针对特定情况进行优化,比如展开循环、消除冗余操作等。
 
-  编译期错误检查 编译期计算还可以用于错误检查和诊断,比如在编译时检测类型不匹配或参数错误,从而避免运行时错误。 
 
-  
元程序的形式:
C++中的元编程是一种在编译时执行代码生成和计算的技术。以下是C++元编程的一些主要形式:
-  模板 模板是C++中实现元编程的核心机制。通过模板,可以定义函数和类,它们可以处理任意类型或值。模板在编译时实例化,根据提供的类型或值参数生成具体的代码。 #include <iostream> #include <type_traits>template <typename T> void print_type_name() {std::cout << "Type: " << typeid(T).name() << std::endl; }int main() {print_type_name<int>(); // 编译时确定类型名称print_type_name<double>();return 0; }
-  constexpr函数 constexpr函数是一种可以在编译时计算并返回常量表达式的函数。这些函数对于实现编译时计算非常有用,因为它们的结果可以被编译器优化和内联。#include <iostream>constexpr int add(int a, int b) {return a + b; }int main() {const int result = add(3, 5); // 编译时计算结果std::cout << "Result: " << result << std::endl;return 0; }
-  编译期可使用的函数 除了模板和 constexpr函数,还有一些内建的编译期函数可以用于元编程,例如:-  sizeof:返回一个类型或对象所占的字节数。int main() { return sizeof(int); // }
-  alignof:返回类型所需的最小对齐字节数。
 
-  
通常以函数为单位,也被称为函数式编程
元数据:(元程序的输入数据)
- 基本元数据:数值、类型、模板
- 数组
元程序的性质:
- 输入输出均为 “ 常量 ”
- 函数无副作用
type_traits元编程库
详细内容可参考:https://en.cppreference.com/w/cpp/header/type_traits
这里面的函数都是编译期可使用的函数
- C++11 引入到标准中,用于元编程的基本组件
二、顺序、分支、循环代码的编写方式
下面介绍元编程的编写方式。
1.顺序代码的编写方式
-  类型转换:去掉引用并添加 const示例: #include <iostream> #include <type_traits>template <typename T, unsigned S> struct Fun {using remRef = typename std::remove_reference<T>::type;using type = typename std::add_const<remRef>::type; };int main() {Fun<int&, 3>::type x = 3; //const int x = 3; }
-  代码无需至于函数中 通常置于模板中,以头文件的形式提供 
-  更复杂的示例: - 以数值、类型、模板作为输入
- 以数值、类型、模板作为输出
 示例: #include <iostream> #include <type_traits>template <typename T, unsigned S> struct Fun {using remRef = typename std::remove_reference<T>::type;constexpr static bool value = (sizeof(T) == 5);};int main() {constexpr bool res = Fun<int&, 4>::value;std::cout << res << "\n"; }
-  引入限定符防止误用 使用限定符(如 const、volatile)可以帮助防止代码的误用
-  通过别名模板简化调用方式 #include <iostream> #include <type_traits>template <typename T, unsigned S> struct Fun {using remRef = typename std::remove_reference<T>::type;constexpr static bool value = (sizeof(T) == 5);};//使用别名模版 template <typename T, int S> constexpr auto Fun_1 = Fun<T, S>::value;int main() {constexpr bool res = Fun_1<int&, 4>;std::cout << res << "\n"; }以 std::is_same举例,详细可参考:https://zh.cppreference.com/w/cpp/types/is_same。
2.分支代码的编写方式
下面介绍六种元编程分支代码的编写方式。
-  基于 if constexpr 的分支:便于理解只能处理数值,同时要小心引入运行期计算 if constexpr是C++17引入的特性,它允许在编译时根据条件编译不同的代码分支。这使得模板代码更加灵活。#include <iostream>template <typename T> void process(T value) {if constexpr (std::is_integral<T>::value) {// 仅当T是整数类型时编译此分支std::cout << "Integral type" << std::endl;} else {// 其他类型std::cout << "Non-integral type" << std::endl;} } int main() {process<int>(100); //Integral type }经编译后的代码为: #include <iostream>template<typename T> void process(T value) {if constexpr(std::is_integral<T>::value) {std::operator<<(std::cout, "Integral type").operator<<(std::endl);} else /* constexpr */ {std::operator<<(std::cout, "Non-integral type").operator<<(std::endl);} }/* First instantiated from: insights.cpp:16 */ #ifdef INSIGHTS_USE_TEMPLATE template<> void process<int>(int value) {if constexpr(true) {std::operator<<(std::cout, "Integral type").operator<<(std::endl);} else /* constexpr */ {} } #endifint main() {process<int>(100);return 0; }
-  基于(偏)特化引入分支:常见分支引入方式但书写麻烦 模板特化可以用来为特定的类型或值提供定制化的实现,这是一种常见的分支引入方式。 #include <iostream>template <typename T> struct Processor;// 对于其他类型,使用默认实现 template <typename T> struct Processor {void process(T value) {// 默认实现std::cout << "process(T value)" << std::endl;} };template <> struct Processor<int> {void process(int value) {// 针对int的特化实现std::cout << "process(int value)" << std::endl;} };int main() {Processor<int> p;p.process(100); //process(int value)Processor<double> p1;p1.process(3.14); //process(T value) }
-  基于 std::conditional 引入分支:语法简单但应用场景受限 详细内容可参考:https://en.cppreference.com/w/cpp/types/conditional 若 B在编译时为 true 则定义为T,或若B为 false 则定义为F。std::conditional是一个编译时条件选择器,可以用来基于模板参数选择不同的类型。#include <iostream> #include <type_traits> #include <typeinfo>int main() {using Type1 = std::conditional<true, int, double>::type;using Type2 = std::conditional<false, int, double>::type;using Type3 = std::conditional<sizeof(int) >= sizeof(double), int, double>::type;std::cout << typeid(Type1).name() << '\n';std::cout << typeid(Type2).name() << '\n';std::cout << typeid(Type3).name() << '\n'; }运行结果: i d d
-  基于 SFINAE 引入分支 -  基于 std::enable_if 引入分支:语法不易懂但功能强大 详细内容可参考:https://zh.cppreference.com/w/cpp/types/enable_if std::enable_if可以用来根据条件启用或禁用模板实例化。#include <iostream>template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>> //为true时等于void void process_integral(T value) {// 仅当T是整数类型时启用此函数std::cout << "111" << std::endl; }int main() {process_integral<int>(100); }注意用做缺省模板实参不能引入分支! 
-  基于 std::void_t 引入分支: C++17 中的新方法,通过 “无效语句” 触发分支 std::void_t是C++17引入的,可以用来在SFINAE中创建一个空类型,从而在条件不满足时使模板实例化失败。
 
-  
-  基于 concept 引入分支: C++20 中的方法 可用于替换 enable_if template <typename T> concept Integral = std::is_integral<T>::value;template <Integral T> void process(T value) {// 仅当T满足Integral概念时编译此分支 }
-  基于三元运算符引入分支: std::conditional 的数值版本 #include <iostream> #include <type_traits>template <int x> constexpr auto fun = (x < 100) ? x*2 : x-3;constexpr auto x = fun<102>();int main() {std::cout << x << std::endl; }
3.循环代码的编写方式
C++元编程中的循环代码编写方式,这是一种在编译时执行循环的技术。
-  简单的示例:计算二进制中包含 1 的个数 #include <iostream> #include <type_traits>template <int x> constexpr auto fun = (x % 2) + fun<x / 2>;template<> constexpr auto fun<0> = 0;constexpr auto x = fun<99>;int main() {std::cout << x << std::endl; }经编译器处理可得到 #include <iostream> #include <type_traits>template<int x> constexpr const auto fun = (x % 2) + fun<x / 2>;template<> constexpr const int fun<99> = (99 % 2) + fun<49>; template<> constexpr const int fun<49> = (49 % 2) + fun<24>; template<> constexpr const int fun<24> = (24 % 2) + fun<12>; template<> constexpr const int fun<12> = (12 % 2) + fun<6>; template<> constexpr const int fun<6> = (6 % 2) + fun<3>; template<> constexpr const int fun<3> = (3 % 2) + fun<1>; template<> constexpr const int fun<1> = (1 % 2) + fun<0>;template<> constexpr const int fun<0> = 0;constexpr const int x = fun<99>;int main() {std::cout.operator<<(x).operator<<(std::endl);return 0; }
-  在编译期通常会使用递归来实现循环 
-  任何一种分支代码的编写方式都对应相应的循环代码编写方式 
三、减少实例化的技巧
-  为什么要减少实例化 - 提升编译速度,减少编译所需内存
 
-  相关技巧 - 提取重复逻辑以减少实例个数
- conditional使用时避免实例化
- 使用std::conjunction / std::disjunction 引入短路逻辑
 
-  其他技巧介绍 - 减少分摊复杂度的数组元素访问操作