February
7th,
2023
泛型编程
长期以来,软件重用一直都是软件工程追求的目标,而泛型编程为软件重用创造了可能性。所谓泛型编程,指的是通过组件的灵活组合来实现软件,而这些组件通过对定义做出最小“假设”来实现最大灵活性。
c++模板
在函数与类的定义 / 声明前,加上模板列表,在列表中指定在函数以及类的定义 / 声明中使用的模板参数
//class/typename定义的T/U是类型参数,size_t定义的S是非类型参数
template<size_t S, class T, typename u>
void fill_container(T& container, U v) {
for (size_t i=0;i<S;++i) {
container.push_back(v);
}
}
- 显示实例化,加快编译速度,其它编译单元直接用显示实例化的符号
template<typename T, typename U> void fill_container(T& container, U v, size_t s) { for (size_t i=0;i<s;++i) { container.push_back(v); } } template void fill_container<std::vector<int>, int>(std::vector<int>&, int, size_t);
- 特化 为特定参数类型提供特定实现版本,提供类似函数重载的支持
template<size_t Size, class T, typename U> void fill(T& collection, U value) { std::cout << "Universal" << std::endl; for (size_t i = 0; i != Size; ++i) { collection.push_back(value); } } template <> void fill<10, std::vector, double>(std::vector& collection, double value) { std::cout << "Explicit (full) template specialization" << std::endl; for (size_t i = 0; i != 10; ++i) { collection.push_back(value + 2.0); } }
- 偏特化
- 不定参模板
double sum() { return 0.0; } template <typename T, typename... Targs> double sum(T value, Targs... Fargs) { return static_cast<double>(value) + sum(Fargs...); }
问题
- 类型约束晦涩难懂
- 生成的代码急速膨胀
- ABI 兼容性糟糕 接口不要使用STL
- 错误消息很难理解
Concepts
解决问题1和4,将一组通用的约束定义为一个concept
,并且,在定义模板函数与模板类中,直接使用这些concept
替换通用的typename
和class
template <参数模板>
concept 名称 = 约束表达式;
class BaseClass {
public:
int32_t getValue() const {
return 1;
}
};
template<class T>
concept DerivedOfBaseClass = std::is_base_of_v<BaseClass, T>;
template <DerivedOfBaseClass T>
void doGetValue(const T& a) {
std::cout << "Get value:" << a.getValue() << std::endl;
}
通过约束与 concept 这两个 C++ 核心语言特性变更(高级抽象),实现了对模板参数列表与参数的约束的逻辑分离
从 C++20 标准及其演进标准之后,concept 之于 C++ 泛型编程,正如 class 之于 C++ 面向对象
约束表达式
通过逻辑操作符的方式进行组合的,用于定义更复杂的 concept
template<class T>
concept Integral = std::is_integral_v<T>;
template<class T>
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
template<class T>
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
template <class T>
concept FloatingPoint = std::is_floating_point_v<T>;
template <class T>
concept Number = Integral<T> || FloatingPoint<T>;
template<typename T>
constexpr bool get_value() { return T::value; }
template<typename T>
requires (sizeof(T) > 1 && get_value<T>())
void f(T) {
std::cout << "template version" << std::endl;
}