C++ 泛型编程指南02 (模板参数的类型推导)
文章目录
- 一 深入了解C++中的函数模板类型推断
- 什么是类型推断?
- 使用Boost TypeIndex库进行类型推断分析
- 示例代码
- 关键点解析
- 2. 理解函数模板类型推断
- 2.1 指针或引用类型
- 2.1.1 忽略引用
- 2.1.2 保持const属性
- 2.1.3 处理指针类型
- 2.2 万能引用类型
- 2.3 传值方式
- 2.4 传值方式的引申—std::ref与std::cref
- 2.5 数组作为实参
- 2.5.1 通过值传递
- 2.5.2 通过引用传递
- 2.6 函数名作为实参
- 2.6.1 通过值传递
- 2.6.2 通过引用传递
- 2.7 初始化列表作为实参
- 2.7.1 包含必要的头文件
- 2.7.2 定义一个接受初始化列表的模板函数
- 2.7.3 使用初始化列表调用函数
- 2.8 类型推断总结
- 2.8.1 引用类型实参的引用属性会被忽略
- 2.8.2 万能引用(通用引用)的推断依赖于实参是左值还是右值
- 2.8.3 按值传递的实参,传递给形参时`const`属性不起作用
- 2.8.4 数组或函数类型在类型推断中默认被视为指针
- 2.8.5 初始化列表必须在函数模板的形参中明确使用`std::initializer_list<T>`
- 三、现代C++的类型推导增强(深度解析)
- 1. `auto` 类型推导机制
- 基本规则
- 推导层次分析
- 工程实践要点
- 2. `decltype` 类型推导系统
- 核心行为
- 关键应用场景
- `decltype(auto)` 深度解析
- 3. 结构化绑定的类型推导(C++17)
- 基本语法形式
- 推导规则体系
- 实现原理
- 工程注意事项
- 4. 推导指南(C++17)
- 核心概念
- 自定义推导指南
- 5. 类型推导的编译时验证
- 静态断言机制
- 概念约束(C++20)
- 类型推导增强的底层原理
- 总结:现代类型推导的演进趋势
一 深入了解C++中的函数模板类型推断
在现代C++编程中,类型推断是一个极其重要的概念,特别是在模板编程领域。它不仅减少了代码的冗余度,还增强了代码的灵活性和可读性。Boost库提供了一种有效的方式来查看和理解这些类型的推断结果,这对于调试和学习都非常有帮助。
什么是类型推断?
类型推断指的是编译器根据函数调用时提供的参数自动确定模板参数的类型。这种机制允许我们编写更简洁和通用的代码,而无需显式地指定所有类型。然而,有时理解编译器是如何进行类型推断的可能并不直观,尤其是在处理引用、指针和常量等复杂情况时。
使用Boost TypeIndex库进行类型推断分析
Boost库提供了一个名为boost::typeindex::type_id_with_cvr<T>()
的功能,它可以返回一个表示类型的对象,该对象可以被用来获取类型的字符串表示,从而使得类型推断的结果更加清晰易读。
示例代码
#include <iostream>
#include <boost/type_index.hpp>template<typename T>
void myfunc(T&& param)
{// 使用Boost TypeIndex库来获取并打印类型std::cout << "T is: " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;std::cout << "param is: " << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << std::endl;
}int main()
{int x = 10;const int cx = x;const int& rx = x;myfunc(x); // 应显示 "int"myfunc(cx); // 应显示 "int const"myfunc(rx); // 应显示 "int const&"myfunc(42); // 应显示 "int"
}
关键点解析
- 安装和配置Boost:确保系统上已经安装了Boost库,并正确配置项目以包含Boost头文件路径。由于
boost::typeindex
是header-only库,因此无需链接任何特定的Boost库。 pretty_name()
方法:相比标准C++的typeid().name()
方法,pretty_name()
提供了更加人类可读的类型名称,这在调试过程中非常有用。- 不同参数的影响:通过传递不同的参数(如普通变量、常量变量、常量引用),我们可以观察到编译器如何对不同类型进行推断,这有助于深入理解模板编程中的类型推断机制。
2. 理解函数模板类型推断
2.1 指针或引用类型
在C++中,当使用函数模板时,编译器通过实参来自动推导模板参数T的类型。这个过程中,对于指针或引用类型的实参,有特定的规则需要了解。
2.1.1 忽略引用
当函数模板的实参是引用类型时,编译器在进行类型推导时会忽略掉引用部分。这意味着模板参数T不会包括引用属性。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>template<typename T>
void myfunc(T& param) {std::cout << "T is: " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;std::cout << "param is: " << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << std::endl;
}int main() {int x = 10;int& k = x;myfunc(k); // T 被推导为 int,而不是 int&
}
在这个示例中,尽管k
是对x
的引用,但是在模板推断中,T
只被推导为int
。
2.1.2 保持const属性
当传递给函数模板的引用类型形参带有const
属性的实参时,这个const
属性会影响到模板参数T的推导。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>template<typename T>
void myfunc(const T& param) {std::cout << "T is: " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;std::cout << "param is: " << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << std::endl;
}int main() {const int j = 20;myfunc(j); // T 被推导为 int, 形参 param 的类型为 const int&
}
在此示例中,j
是const int
类型,T
被正确推导为int
,而形参param
的类型是const int&
。这确保了j
的常量性质不会被修改。
2.1.3 处理指针类型
对于指针类型的形参,类型推断也遵循特定的规则,尤其是在处理const
修饰符时。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>template <typename T>
void myfunc(T* tmprv) {std::cout << "T is: " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;std::cout << "param is: " << boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name() << std::endl;
}int main() {int i = 18;const int* pi = &i;myfunc(&i); // 查看实际执行结果:T=int, tmprv=int*myfunc(pi); // 查看实际执行结果:T=int const, tmprv=int const*
}
- 当调用
myfunc(&i)
时,&i
是一个指向int
的指针,因此T
被推导为int
,而tmprv
的类型为int*
。 - 当调用
myfunc(pi)
时,pi
是一个指向const int
的指针,因此T
被推导为int const
,而tmprv
的类型为int const*
。
2.2 万能引用类型
万能引用(Universal References),也称为转发引用(Forwarding References),是一种特殊的引用类型,在模板函数中通过T&&
声明。C++的引用折叠规则使得万能引用可以根据传入的实参类型(左值或右值)来决定其最终类型:
- 绑定到左值:当一个左值被传递给万能引用时,万能引用将被推导为左值引用(
T&
)。这允许函数处理持久的对象,而不仅仅是临时值。 - 绑定到右值:当一个右值被传递给万能引用时,万能引用将被推导为右值引用(
T&&
)。这使得函数能够优化资源管理,例如通过移动语义避免不必要的复制。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>template<typename T>
void printType(T&& param) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name()<< std::endl;
}int main() {int x = 10;printType(x); // 输出x为左值的类型信息printType(10); // 输出10为右值的类型信息
}
printType(x)
会打印出T
为int&
(因为x
是一个左值)和param
也为int&
。printType(10)
会打印出T
为int
(因为10
是一个右值)和param
为int&&
。
2.3 传值方式
在C++中,当函数参数以传值方式接收时,无论原始对象是什么类型(包括指针、引用或常规变量),传递给函数的都是该对象的一个副本。这意味着在函数内部对这个副本的任何修改都不会影响到原始对象。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>template <typename T>
void myfunc(T tmprv) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {int i = 18;const int j = i;const int& k = i;myfunc(i); // 实际执行结果:T=int, tmprv=intmyfunc(j); // 实际执行结果:T=int, tmprv=int,const 属性没有传递,因为对方是新副本myfunc(k); // 实际执行结果:T=int, tmprv=int,const 属性和引用都没有传递,因为对方是新副本
}
结论:
- 忽略引用性:若实参是引用类型,则引用部分会被忽略,
T
不会被推导为引用类型。 - 忽略顶层
const
:若实参是const
类型,该const
属性在类型推导时会被忽略,因为传递的是新副本。
2.4 传值方式的引申—std::ref与std::cref
为了减少不必要的数据复制,C++11提供了std::ref
和std::cref
,它们定义在<functional>
头文件中。这两个工具允许以引用的形式传递参数,而不是复制对象:
std::ref
用于创建一个对象的引用。std::cref
用于创建一个对象的常量引用。
示例代码:
#include <iostream>
#include <functional>
#include <boost/type_index.hpp>template<typename T>
void myfunc(T tmprv) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {int x = 10;const int y = 20;// 传值方式myfunc(x);myfunc(y);// 使用 std::ref 和 std::crefmyfunc(std::ref(x));myfunc(std::cref(y));return 0;
}
myfunc(x)
和myfunc(y)
通过值传递调用,T
被推导为int
和const int
。myfunc(std::ref(x))
和myfunc(std::cref(y))
通过引用传递调用,T
被推导为int&
和const int&
。
2.5 数组作为实参
当数组被用作函数模板的实参时,其处理方式依赖于参数传递的方式:是通过值还是通过引用。
2.5.1 通过值传递
在C++中,当数组作为函数参数通过值传递时,它不会将整个数组的副本传递给函数,而是只传递一个指向数组首元素的指针,这种现象称为“数组退化”。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>template <typename T>
void myfunc(T tmprv) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {const char mystr[] = "I Love China!";myfunc(mystr); // 实际执行结果:T=const char*, tmprv=const char*
}
这里,mystr
被退化为const char*
,因此T
是const char*
。
2.5.2 通过引用传递
修改函数模板使其通过引用接收参数可以保留数组的完整性,避免退化为指针。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>template <typename T>
void myfunc(T& tmprv) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {const char mystr[] = "I Love China!";myfunc(mystr); // 实际执行结果:T=const char [14], tmprv=const char (&)[14]
}
在这种情况下,数组不会退化,T
被推导为具体的数组类型const char [14]
,而tmprv
是该数组的引用。
2.6 函数名作为实参
在C++中,函数名可以用作函数模板的实参,在编写需要回调函数的代码或实现高阶函数时,经常需要将函数名作为参数传递给其他函数。使用模板可以使这类函数更加通用和灵活。
2.6.1 通过值传递
当函数名作为实参传递时,默认被视为指向该函数的指针。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>void testFunc() {}template <typename T>
void myfunc(T tmprv) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {myfunc(testFunc); // 实际执行结果:T=void (*)(void), tmprv=void (*)(void)
}
这里,testFunc
被视为void (*)(void)
类型,即指向无参、无返回值函数的指针。
2.6.2 通过引用传递
通过修改模板参数为引用类型,可以获取到函数的引用,而非指针。
示例代码:
#include <iostream>
#include <boost/type_index.hpp>void testFunc() {}template <typename T>
void myfunc(T& tmprv) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {myfunc(testFunc); // 实际执行结果:T=void (&)(void), tmprv=void (&)(void)
}
在这种情况下,T
和tmprv
被推导为函数的引用类型void (&)(void)
,这显示了C++对函数的引用支持。
2.7 初始化列表作为实参
在C++中,初始化列表(std::initializer_list
)提供了一种方便的方法来处理未知数量的同类型参数。这在模板编程中尤其有用,因为它允许函数接受任意数量的同类型参数,而无需预先定义参数的数量。
2.7.1 包含必要的头文件
要使用std::initializer_list
,首先需要包含相应的头文件。通常,这会是<initializer_list>
,它定义了std::initializer_list
类。此外,为了进行输出等操作,可以包含<iostream>
头文件:
#include <initializer_list>
#include <iostream>
#include <boost/type_index.hpp> // 需要包含 Boost TypeIndex 头文件
2.7.2 定义一个接受初始化列表的模板函数
你可以定义一个模板函数,该函数接受一个类型为std::initializer_list<T>
的参数。这允许你传递一个由花括号 {}
包围的元素列表给函数,如下所示:
template <typename T>
void myfunc(std::initializer_list<T> tmprv) {for (const auto& item : tmprv) {std::cout << item << " ";}std::cout << std::endl;// 打印类型信息std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}
在这个函数中,遍历初始化列表中的每个元素,并将其打印出来。通过打印出每个元素的类型和参数的类型,可以更深入地理解类型推断和模板的行为。使用基于范围的for循环使代码简洁且易于理解。
2.7.3 使用初始化列表调用函数
在主函数中,你可以通过以下方式调用myfunc
,传入一个初始化列表:
int main() {myfunc({1, 2, 3, 4, 5}); // 调用模板函数并传入一个整数列表myfunc({"apple", "banana", "cherry"}); // 调用相同的模板函数并传入一个字符串列表
}
这样的调用方式表明,myfunc
能够接受任何类型的元素列表,只要这些元素类型相同。每次调用时,模板参数T
被推导为列表中元素的类型,无论是int
、std::string
还是其他任何类型。
2.8 类型推断总结
在C++模板编程中,类型推断遵循一些特定的规则,这些规则决定了如何根据实参推导模板参数的类型。以下是关于类型推断的一些关键点总结:
2.8.1 引用类型实参的引用属性会被忽略
在类型推断过程中,如果实参是引用类型,其引用属性会被忽略。这意味着不论实参是左值引用还是右值引用,都会被视为其底层类型进行推断。
示例:
template<typename T>
void myfunc(T& param) {std::cout << "T is: " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
}int x = 10;
int& k = x;
myfunc(k); // T 被推导为 int,而不是 int&
2.8.2 万能引用(通用引用)的推断依赖于实参是左值还是右值
当模板参数按值传递时,实参的const
属性不影响推断结果,因此const
修饰符会被忽略。然而,如果传递的是指向const
的指针或引用,其const
属性仍然保留。
示例:
template<typename T>
void printType(T&& param) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name()<< std::endl;
}int main() {int x = 10;printType(x); // 输出x为左值的类型信息printType(10); // 输出10为右值的类型信息
}
printType(x)
会打印出T
为int&
(因为x
是一个左值)和param
也为int&
。printType(10)
会打印出T
为int
(因为10
是一个右值)和param
为int&&
。
2.8.3 按值传递的实参,传递给形参时const
属性不起作用
当模板参数按值传递时,实参的const
属性不影响推断结果,因此const
修饰符会被忽略。然而,如果传递的是指向const
的指针或引用,其const
属性仍然保留。
示例:
template <typename T>
void myfunc(T tmprv) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {const int j = 20;myfunc(j); // 实际执行结果:T=int, tmprv=int,const 属性没有传递,因为对方是新副本
}
2.8.4 数组或函数类型在类型推断中默认被视为指针
在类型推断中,数组或函数名将退化为相应的指针类型,除非模板形参明确声明为引用类型,这时候不会发生退化。
示例:
template <typename T>
void myfunc(T tmprv) {std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {const char mystr[] = "I Love China!";myfunc(mystr); // 实际执行结果:T=const char*, tmprv=const char*
}
2.8.5 初始化列表必须在函数模板的形参中明确使用std::initializer_list<T>
std::initializer_list
类型无法自动从花括号初始化列表推断得出,必须在函数模板的形参中显式声明为std::initializer_list<T>
类型。
示例:
template <typename T>
void myfunc(std::initializer_list<T> tmprv) {for (const auto& item : tmprv) {std::cout << item << " ";}std::cout << std::endl;// 打印类型信息std::cout << "T is: "<< boost::typeindex::type_id_with_cvr<T>().pretty_name()<< std::endl;std::cout << "param is: "<< boost::typeindex::type_id_with_cvr<decltype(tmprv)>().pretty_name()<< std::endl;
}int main() {myfunc({1, 2, 3, 4, 5}); // 调用模板函数并传入一个整数列表myfunc({"apple", "banana", "cherry"}); // 调用相同的模板函数并传入一个字符串列表
}
三、现代C++的类型推导增强(深度解析)
现代C++(C++11及后续标准)对类型推导机制进行了重大增强,极大提升了代码的表达能力和安全性。以下从核心机制、典型应用和底层原理三个维度深入解析这些增强特性。
1. auto
类型推导机制
基本规则
- 遵循模板类型推导规则:与模板函数参数推导机制一致
- 例外处理:对初始化列表的特殊处理
auto x = 5; // int
auto y = {1, 2, 3}; // std::initializer_list<int>(C++11特有规则)
推导层次分析
-
基本推导:
const int ci = 42; auto a = ci; // int(丢弃顶层const) auto& b = ci; // const int&(保留底层const)
-
引用折叠应用:
int i = 10; auto&& r1 = i; // int&(左值推导) auto&& r2 = 42; // int&&(右值推导)
-
指针与数组处理:
const char name[] = "C++"; auto arr1 = name; // const char*(数组退化为指针) auto& arr2 = name; // const char(&)[4](保留数组类型)
工程实践要点
- 性能优化:
std::vector<std::string> heavyData; for (const auto& elem : heavyData) { ... } // 避免拷贝
- 类型精确控制:
auto ptr = static_cast<float*>(malloc(100 * sizeof(float))); // 明确指针类型
2. decltype
类型推导系统
核心行为
- 精确保留表达式类型(包括CV限定符和引用属性)
int x = 0;
const int& crx = x;
decltype(x) a; // int
decltype(crx) b; // const int&
decltype((x)) c; // int&(注意括号的影响)
关键应用场景
-
返回值类型推导:
template<typename T1, typename T2> auto add(T1 a, T2 b) -> decltype(a + b) {return a + b; }
-
类型关系维护:
template<typename Container> auto getElement(Container& c, size_t index) -> decltype(c[index]) {return c[index]; // 完美保留返回类型(可能为引用) }
-
元编程支持:
template<typename T> using RemoveReference = typename std::remove_reference<decltype(std::declval<T>())>::type;
decltype(auto)
深度解析
- 设计目标:在单表达式场景中完美转发类型信息
- 典型用例:
template<typename F, typename... Args> decltype(auto) callFunc(F&& f, Args&&... args) {return std::forward<F>(f)(std::forward<Args>(args)...); }
- 与
auto
对比:const int x = 42; auto a = x; // int decltype(auto) b = x; // const int auto& c = x; // const int& decltype(auto) d = (x);// const int&(注意括号的语义变化)
3. 结构化绑定的类型推导(C++17)
基本语法形式
auto [var1, var2, ...] = expression;
auto& [var1, var2, ...] = expression;
推导规则体系
-
绑定到非嵌套类型:
std::pair<int, std::string> p{42, "answer"}; auto& [num, text] = p; // num: int&, text: std::string&
-
绑定到结构体成员:
struct Point { double x, y; }; Point pt{1.0, 2.0}; const auto [a, b] = pt; // a: const double, b: const double
-
绑定到数组元素:
int arr[] = {1, 2, 3}; auto [x, y, z] = arr; // x,y,z: int auto& [rx, ry, rz] = arr; // rx,ry,rz: int&
实现原理
- 隐藏代理对象:编译器生成匿名结构体保存引用
- 访问器方法:实际通过
get<N>
系列函数实现访问
工程注意事项
- 生命周期管理:
auto getData() -> std::tuple<std::vector<int>, std::string>;auto [vec, str] = getData(); // vec和str是拷贝的独立对象 auto& [rvec, rstr] = getData(); // 危险!临时对象立即销毁
4. 推导指南(C++17)
核心概念
- 用户自定义推导规则:指导类模板参数推导
- 标准库应用示例:
std::vector v{1, 2, 3}; // 推导为vector<int> std::mutex mtx; std::lock_guard lck(mtx); // 推导为lock_guard<mutex>
自定义推导指南
template<typename T>
struct CustomWrapper {template<typename U>CustomWrapper(U&& u) : t(std::forward<U>(u)) {}T t;
};// 用户定义的推导指南
template<typename U>
CustomWrapper(U) -> CustomWrapper<std::decay_t<U>>;
5. 类型推导的编译时验证
静态断言机制
template<typename T>
void process(T&& param) {static_assert(std::is_integral_v<std::decay_t<T>>,"Requires integral type");
}
概念约束(C++20)
template<std::integral T>
auto safe_divide(T a, T b) -> std::optional<T> {if (b == 0) return std::nullopt;return a / b;
}
类型推导增强的底层原理
-
编译器前端处理:
- 词法分析阶段识别类型推导关键字
- 语法分析阶段构建推导上下文
-
类型系统交互:
- 结合重载决议规则(Overload Resolution)
- 引用折叠(Reference Collapsing)的实现
-
模板实例化过程:
- 两阶段查找(Two-phase lookup)的影响
- SFINAE(Substitution Failure Is Not An Error)机制
总结:现代类型推导的演进趋势
特性 | C++11 | C++14 | C++17 | C++20 |
---|---|---|---|---|
auto | 基本推导规则 | 函数返回类型推导 | 非类型模板参数推导 | 概念约束 |
decltype | 基本功能 | decltype(auto) | 结构化绑定中的推导 | 更精细的类型特征检查 |
推导指南 | N/A | N/A | 类模板参数推导 | 增强的CTAD规则 |
模式匹配 | 基础模板元编程 | 改进的SFINAE | 结构化绑定 | 模式匹配提案推进 |
通过掌握这些增强特性,开发者可以:
- 编写更简洁、类型安全的泛型代码
- 实现高效的资源管理(避免不必要的拷贝)
- 构建更灵活的接口设计
- 提升模板元编程的表达能力
- 更好地与现代C++标准库协同工作
建议通过编译器资源管理器(Compiler Explorer)实时观察不同类型推导的结果,结合标准文档深入理解各个特性的设计哲学和实现细节。
相关文章:
C++ 泛型编程指南02 (模板参数的类型推导)
文章目录 一 深入了解C中的函数模板类型推断什么是类型推断?使用Boost TypeIndex库进行类型推断分析示例代码关键点解析 2. 理解函数模板类型推断2.1 指针或引用类型2.1.1 忽略引用2.1.2 保持const属性2.1.3 处理指针类型 2.2 万能引用类型2.3 传值方式2.4 传值方式…...
穷举vs暴搜vs深搜vs回溯vs剪枝系列一>单词搜索
题解如下 题目:解析决策树:代码设计: 代码: 题目: 解析 决策树: 代码设计: 代码: class Solution {private boolean[][] visit;//标记使用过的数据int m,n;//行,列char…...
9 点结构模块(point.rs)
一、point.rs源码 use super::UnknownUnit; use crate::approxeq::ApproxEq; use crate::approxord::{max, min}; use crate::length::Length; use crate::num::*; use crate::scale::Scale; use crate::size::{Size2D, Size3D}; use crate::vector::{vec2, vec3, Vector2D, V…...
基于vue船运物流管理系统设计与实现(源码+数据库+文档)
船运物流管理系统目录 目录 基于springboot船运物流管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员登录 2、货运单管理 3、公告管理 4、公告类型管理 5、新闻管理 6、新闻类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考…...
海思ISP开发说明
1、概述 ISP(Image Signal Processor)图像信号处理器是专门用于处理图像信号的硬件或处理单元,广泛应用于图像传感器(如 CMOS 或 CCD 传感器)与显示设备之间的信号转换过程中。ISP通过一系列数字图像处理算法完成对数字…...
【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.12 连续数组:为什么contiguous这么重要?
2.12 连续数组:为什么contiguous这么重要? 目录 #mermaid-svg-wxhozKbHdFIldAkj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wxhozKbHdFIldAkj .error-icon{fill:#552222;}#mermaid-svg-…...
Chromium132 编译指南 - Android 篇(一):编译前准备
1. 引言 欢迎来到《Chromium 132 编译指南 - Android 篇》系列的第一部分。本系列指南将引导您逐步完成在 Android 平台上编译 Chromium 132 版本的全过程。Chromium 作为一款由 Google 主导开发的开源浏览器引擎,为众多现代浏览器提供了核心驱动力。而 Android 作…...
Jenkins未在第一次登录后设置用户名,第二次登录不进去怎么办?
Jenkins在第一次进行登录的时候,只需要输入Jenkins\secrets\initialAdminPassword中的密码,登录成功后,本次我们没有修改密码,就会导致后面第二次登录,Jenkins需要进行用户名和密码的验证,但是我们根本就没…...
#define,源文件与头文件,赋值表达式
1.#define 1.1定义 #define 是一个预处理指令,用于定义宏 宏,是预处理阶段(在编译之前)由预处理器处理的代码片段 1.2使用 1.2.1 #define 可以定义常量 #define PI 3.14159 1.2.2 #define 可以定义宏函数 #define SQUARE(x) ((…...
wordpress外贸独立站常用询盘软件
LiveChat LiveChat是一家提供实时聊天软件的公司,帮助企业通过其平台与客户进行即时通讯,提高客户满意度和忠诚度。他们的产品允许企业在网站、应用程序或电子邮件等多个渠道与客户互动,从而提升客户体验并促进销售增长。 LiveChat的软件特…...
网络爬虫学习:应用selenium获取Edge浏览器版本号,自动下载对应版本msedgedriver,确保Edge浏览器顺利打开。
一、前言 我从24年11月份开始学习网络爬虫应用开发,经过2个来月的努力,于1月下旬完成了开发一款网络爬虫软件的学习目标。这里对本次学习及应用开发进行一下回顾总结。 前几天我已经发了一篇日志(网络爬虫学习:应用selenium从搜…...
Zookeeper入门部署(单点与集群)
本篇文章基于docker方式部署zookeeper集群,请先安装docker 目录 1. docker初期准备 2.启动zookeeper 2.1 单点部署 2.2 集群部署 3. Linux脚本实现快速切换启动关闭 1. docker初期准备 拉取zookeeper镜像 docker pull zookeeper:3.5.6 如果拉取时间过长…...
2025最新在线模型转换工具onnx转换ncnn,mnn,tengine等
文章目录 引言最新网址地点一、模型转换1. 框架转换全景图2. 安全的模型转换3. 网站全景图 二、转换说明三、模型转换流程图四、感谢 引言 在yolov5,yolov8,yolov11等等模型转换的领域中,时间成本常常是开发者头疼的问题。最近发现一个超棒的…...
文件读写操作
写入文本文件 #include <iostream> #include <fstream>//ofstream类需要包含的头文件 using namespace std;void test01() {//1、包含头文件 fstream//2、创建流对象ofstream fout;/*3、指定打开方式:1.ios::out、ios::trunc 清除文件内容后打开2.ios:…...
AJAX XML
AJAX XML 引言 随着互联网技术的不断发展,Web应用对用户交互性和实时性的要求越来越高。AJAX(Asynchronous JavaScript and XML)技术的出现,为Web应用开发提供了强大的支持。AJAX技术允许Web应用在不重新加载整个页面的情况下,与服务器进行异步通信。XML作为数据传输格式…...
Linux文件原生操作
Linux 中一切皆文件,那么 Linux 文件是什么? 在 Linux 中的文件 可以是:传统意义上的有序数据集合,即:文件系统中的物理文件 也可以是:设备,管道,内存。。。(Linux 管理的一切对象…...
JavaScript常用的内置构造函数
JavaScript作为一种广泛应用的编程语言,提供了丰富的内置构造函数,帮助开发者处理不同类型的数据和操作。这些内置构造函数在创建和操作对象时非常有用。本文将详细介绍JavaScript中常用的内置构造函数及其用途。 常用内置构造函数概述 1. Object Obj…...
Shell篇-字符串处理
目录 1.变量引用 2.获取字符串长度 3.字符串截取 4.删除子字符串 5.字符串替换 总结: Bash(Shell 脚本)中的字符串处理语法。以下是对其的介绍和总结:Bash 变量可以使用不同的语法来获取、修改和删除字符串的内容。图片中列…...
Arduino大师练成手册 -- 控制 AS608 指纹识别模块
要在 Arduino 上控制 AS608 指纹识别模块,你可以按照以下步骤进行: 硬件连接 连接指纹模块:将 AS608 指纹模块与 Arduino 连接。通常,AS608 使用 UART 接口进行通信。你需要将 AS608 的 TX、RX、VCC 和 GND 引脚分别连接到 Ardu…...
C++ Primer 命名空间的using声明
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
C#,入门教程(12)——数组及数组使用的基础知识
上一篇: C#,入门教程(11)——枚举(Enum)的基础知识和高级应用https://blog.csdn.net/beijinghorn/article/details/123917587https://blog.csdn.net/beijinghorn/article/details/123917587 数组是一种数据集合,是一组…...
深入浅出并查集(不相交集合实现思路)
引言 并查集(Disjoint Set Union,简称DSU)是一种用于处理一些不交集的合并及查询问题。它主要支持两种操作:查找(Find)和合并(Union)。 查找:确定某个元素属于哪一个子…...
M|哪吒之魔童闹海
rating: 8.5 豆瓣: 8.5 上映时间: “2025” 类型: M动画 导演: 饺子 主演: 国家/地区: 中国大陆 片长/分钟: 144分钟 M|哪吒之魔童闹海 制作精良,除了剧情逻辑有一点瑕疵,各方面都很到位。总体瑕不掩瑜。 上映时间: &…...
【c++】类与对象详解
目录 面向过程思想和面向对象思想类的定义引入类的关键字类定义的两种方式类的访问限定符类的作用域类大小的计算封装 this指针类的6个默认成员函数构造函数初步理解构造函数深入理解构造函数初始化列表单参数构造函数引发的隐式类型转换 析构函数拷贝构造函数赋值运算符重载运…...
LabVIEW如何有效地进行数据采集?
数据采集(DAQ)是许多工程项目中的核心环节,无论是测试、监控还是控制系统,准确、高效的数据采集都是至关重要的。LabVIEW作为一个图形化编程环境,提供了丰富的功能来实现数据采集,确保数据的实时性与可靠性…...
Golang 并发机制-4:用Mutex管理共享资源
并发性是Go的强大功能之一,它允许多个线程(并发线程)同时执行。然而,权力越大,责任越大。当多个例程并发地访问和修改共享资源时,可能会导致数据损坏、竞争条件和不可预测的程序行为。为了解决这些问题&…...
如何用微信小程序写春联
生活没有模板,只需心灯一盏。 如果笑能让你释然,那就开怀一笑;如果哭能让你减压,那就让泪水流下来。如果沉默是金,那就不用解释;如果放下能更好地前行,就别再扛着。 一、引入 Vant UI 1、通过 npm 安装 npm i @vant/weapp -S --production 2、修改 app.json …...
从零开始:用Qt开发一个功能强大的文本编辑器——WPS项目全解析
文章目录 引言项目功能介绍1. **文件操作**2. **文本编辑功能**3. **撤销与重做**4. **剪切、复制与粘贴**5. **文本查找与替换**6. **打印功能**7. **打印预览**8. **设置字体颜色**9. **设置字号**10. **设置字体**11. **左对齐**12. **右对齐**13. **居中对齐**14. **两侧对…...
LLMs之OpenAI o系列:OpenAI o3-mini的简介、安装和使用方法、案例应用之详细攻略
LLMs之OpenAI o系列:OpenAI o3-mini的简介、安装和使用方法、案例应用之详细攻略 目录 相关文章 LLMs之o3:《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读 LLMs之OpenAI o系列:OpenAI o3-mini的简介、安…...
DeepSeek-R1 低成本训练的根本原因是?
在人工智能领域,大语言模型(LLM)正以前所未有的速度发展,驱动着自然语言处理、内容生成、智能客服等众多应用的革新。然而,高性能的背后往往是高昂的训练成本,动辄数百万美元的投入让许多企业和研究机构望而…...
C语言:结构体
一,结构体 C语⾔已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设我想描述学⽣,描述⼀本书,这时单⼀的内置类型是不⾏的。 描述⼀个学⽣需…...
java练习(5)
ps:题目来自力扣 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这…...
【高等数学】贝塞尔函数
贝塞尔函数(Bessel functions)是数学中一类重要的特殊函数,通常用于解决涉及圆对称或球对称的微分方程。它们在物理学、工程学、天文学等多个领域都有广泛的应用,例如在波动方程、热传导方程、电磁波传播等问题中。 贝塞尔函数的…...
贪吃蛇实现
1.资料来源 https://learn.microsoft.com/zh-cn/windows/console/getstdhandle 2.前言 简介 贪吃蛇是久负盛名的游戏,和俄罗斯方块、扫雷等游戏位列于经典游戏的行列。 《贪食蛇》中玩家控制一条不断移动的蛇,在屏幕上吃掉出现的食物。每吃掉一个食物…...
Windows电脑本地部署运行DeepSeek R1大模型(基于Ollama和Chatbox)
文章目录 一、环境准备二、安装Ollama2.1 访问Ollama官方网站2.2 下载适用于Windows的安装包2.3 安装Ollama安装包2.4 指定Ollama安装目录2.5 指定Ollama的大模型的存储目录 三、选择DeepSeek R1模型四、下载并运行DeepSeek R1模型五、使用Chatbox进行交互5.1 下载Chatbox安装包…...
在C++中,成员变量必须在对象构造完成前初始化,但初始化的方式有多种...
在C中,成员变量必须在对象构造完成前初始化,但初始化的方式可以有多种,具体取决于成员变量的类型和设计需求。以下是C中成员变量初始化的规则和相关机制: 1. 成员变量必须初始化 如果成员变量是基本类型(如 int、doub…...
maven mysql jdk nvm node npm 环境安装
安装JDK 1.8 11 环境 maven环境安装 打开网站 下载 下载zip格式 解压 自己创建一个maven库 以后在idea 使用maven时候重新设置一下 这三个地方分别设置 这时候maven才算设置好 nvm 管理 npm nodejs nvm下载 安装 Releases coreybutler/nvm-windows GitHub 一键安装且若有…...
算法随笔_37: 交替合并字符串
上一篇:算法随笔_36: 复写零-CSDN博客 题目描述如下: 给你两个字符串 word1 和 word2 。请你从 word1 开始,通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长,就将多出来的字母追加到合并后字符串的末尾。 返回 合并后的字符串 。 示例…...
w188校园商铺管理系统设计与实现
🙊作者简介:多年一线开发工作经验,原创团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹赠送计算机毕业设计600个选题excel文…...
(2025 年最新)MacOS Redis Desktop Manager中文版下载,附详细图文
MacOS Redis Desktop Manager中文版下载 大家好,今天给大家带来一款非常实用的 Redis 可视化工具——Redis Desktop Manager(简称 RDM)。相信很多开发者都用过 Redis 数据库,但如果你想要更高效、更方便地管理 Redis 数据&#x…...
【Block总结】Shuffle Attention,新型的Shuffle注意力|即插即用
一、论文信息 标题: SA-Net: Shuffle Attention for Deep Convolutional Neural Networks 论文链接: arXiv 代码链接: GitHub 二、创新点 Shuffle Attention(SA)模块的主要创新在于高效结合了通道注意力和空间注意力,同时通过通道重排技…...
解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路
前瞻 PosterBandit 这个脚本能够根据用户指定的日期,爬取你看过的影视最高清的海报,然后使用 PixelWeaver.py 自动拼接成指定大小的长图。 你是否发现直接从豆瓣爬取下来的海报清晰度很低? 使用 .pic .nbg img CSS 选择器,在 我…...
【机器学习与数据挖掘实战】案例11:基于灰色预测和SVR的企业所得税预测分析
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…...
聚簇索引、哈希索引、覆盖索引、索引分类、最左前缀原则、判断索引使用情况、索引失效条件、优化查询性能
聚簇索引 聚簇索引像一本按目录排版的书,用空间换时间,适合读多写少的场景。设计数据库时,主键的选择(如自增ID vs 随机UUID)会直接影响聚簇索引的性能。 什么是聚簇索引? 数据即索引:聚簇索引…...
克隆OpenAI(基于openai API和streamlit)
utils.py: from langchain_openai import ChatOpenAI from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain import osdef get_chat_response(api_key,prompt,memory): # memory不能是函数的内部局部变量&…...
DeepSeek技术深度解析:从不同技术角度的全面探讨
DeepSeek技术深度解析:从不同技术角度的全面探讨 引言 DeepSeek是一个集成了多种先进技术的平台,旨在通过深度学习和其他前沿技术来解决复杂的问题。本文将从算法、架构、数据处理以及应用等不同技术角度对DeepSeek进行详细分析。 一、算法层面 深度学…...
完全卸载mysql server步骤
1. 在控制面板中卸载mysql 2. 打开注册表,运行regedit, 删除mysql信息 HKEY_LOCAL_MACHINE-> SYSTEM->CurrentContolSet->Services->EventLog->Application->Mysql HKEY_LOCAL_MACHINE-> SYSTEM->CurrentContolSet->Services->Mysql …...
2025年大年初一篇,C#调用GPU并行计算推荐
C#调用GPU库的主要目的是利用GPU的并行计算能力,加速计算密集型任务,提高程序性能,支持大规模数据处理,优化资源利用,满足特定应用场景的需求,并提升用户体验。在需要处理大量并行数据或进行复杂计算的场景…...
机器学习优化算法:从梯度下降到Adam及其实验改进
机器学习优化算法:从梯度下降到Adam及其实验改进 在机器学习和深度学习领域,模型的训练过程本质上是一个优化问题。优化算法的作用是通过调整模型参数,使得模型在给定的数据 集上实现最优性能。而优化算法的效率和效果直接决定了模型的收敛速…...
在 Ubuntu 中使用 Conda 创建和管理虚拟环境
Conda 是一个广泛使用的包管理和环境管理系统,尤其适用于数据科学和 Python 开发。本文将指导你如何在 Ubuntu 系统中安装 Conda 并创建基于 python3.11 的虚拟环境。 1. 安装 Miniconda 或 Anaconda 方法 1:下载并安装 Miniconda Miniconda 是一个轻量…...