C++20中的<concepts>头文件详解
C++20引入了概念(Concepts),这是一种新的类型约束机制,用于模板编程中。概念允许程序员对模板参数施加更精确的要求,从而提升代码的类型安全性和可读性。<concepts>头文件包含了一系列预定义的概念,这些概念可以直接用于模板代码中。本文将详细介绍<concepts>头文件中的主要内容及其用法。
什么是概念?
概念是一种模板元编程的工具,用于指定模板参数必须满足的条件。通过概念,可以在模板代码中明确地表达类型约束,使得编译器能够在编译期检查这些约束,从而提高代码的安全性。
<concepts>头文件中的主要概念
1. 核心语言概念
std::same_as
std::same_as用于检查两个类型是否相同。
#include <concepts>
#include <iostream>
template <typename T, typename U>
requires std::same_as<T, U>
void foo(T t, U u) {
std::cout << "Types are the same." << std::endl;
}
int main() {
foo(1, 1); // 合法
// foo(1, 1.0); // 编译错误,类型不同
return 0;
}
std::convertible_to
std::convertible_to用于检查一个类型是否可以隐式转换为另一个类型。
#include <concepts>
#include <iostream>
template <typename T, typename U>
requires std::convertible_to<T, U>
U convert(T t) {
return t;
}
int main() {
std::cout << convert<int, double>(42) << std::endl; // 合法
// std::cout << convert<double, int>("42") << std::endl; // 编译错误,类型不可转换
return 0;
}
std::derived_from
std::derived_from用于检查一个类型是否从另一个类型派生。
#include <concepts>
#include <iostream>
class Base {};
class Derived : public Base {};
template <typename T, typename U>
requires std::derived_from<T, U>
void check_derived(T t, U u) {
std::cout << "T is derived from U." << std::endl;
}
int main() {
Derived d;
Base b;
check_derived(d, b); // 合法
// check_derived(b, d); // 编译错误,Base不是Derived的派生类
return 0;
}
2. 对象和引用概念
std::destructible
std::destructible用于检查一个类型是否可以被销毁。
#include <concepts>
#include <iostream>
class Test {
public:
~Test() = default;
};
template <typename T>
requires std::destructible<T>
void destroy(T& t) {
t.~T();
std::cout << "Object destroyed." << std::endl;
}
int main() {
Test t;
destroy(t); // 合法
return 0;
}
std::constructible_from
std::constructible_from用于检查一个类型是否可以用指定的参数类型构造。
#include <concepts>
#include <iostream>
class Test {
public:
Test(int, double) {}
};
template <typename T, typename... Args>
requires std::constructible_from<T, Args...>
T create(Args... args) {
return T(args...);
}
int main() {
Test t = create<Test>(42, 3.14); // 合法
// Test t2 = create<Test>(42); // 编译错误,参数数量不匹配
return 0;
}
std::default_initializable
std::default_initializable用于检查一个类型是否可以默认初始化。
#include <concepts>
#include <iostream>
class Test {
public:
Test() = default;
};
template <typename T>
requires std::default_initializable<T>
T create_default() {
return T();
}
int main() {
Test t = create_default<Test>(); // 合法
return 0;
}
3. 算术概念
std::integral
std::integral用于检查一个类型是否是整型。
#include <concepts>
#include <iostream>
template <typename T>
requires std::integral<T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << std::endl; // 合法
// std::cout << add(1.0, 2.0) << std::endl; // 编译错误,参数不是整型
return 0;
}
std::signed_integral和std::unsigned_integral
这些概念分别用于检查一个类型是否是有符号整型或无符号整型。
#include <concepts>
#include <iostream>
template <typename T>
requires std::signed_integral<T>
T negate(T a) {
return -a;
}
int main() {
std::cout << negate(-5) << std::endl; // 合法
// std::cout << negate(5u) << std::endl; // 编译错误,参数不是有符号整型
return 0;
}
std::floating_point
std::floating_point用于检查一个类型是否是浮点型。
#include <concepts>
#include <iostream>
template <typename T>
requires std::floating_point<T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1.5, 2.5) << std::endl; // 合法
// std::cout << add(1, 2) << std::endl; // 编译错误,参数不是浮点型
return 0;
}
4. 可比较概念
std::equality_comparable
std::equality_comparable用于检查一个类型是否支持相等比较。
#include <concepts>
#include <iostream>
template <typename T>
requires std::equality_comparable<T>
bool are_equal(T a, T b) {
return a == b;
}
int main() {
std::cout << are_equal(1, 1) << std::endl; // 合法
return 0;
}
std::totally_ordered
std::totally_ordered用于检查一个类型是否支持完全排序比较(小于、大于、小于等于、大于等于)。
#include <concepts>
#include <iostream>
template <typename T>
requires std::totally_ordered<T>
bool is_less(T a, T b) {
return a < b;
}
int main() {
std::cout << is_less(1, 2) << std::endl; // 合法
return 0;
}
5. 范围概念
<concepts>头文件与<ranges>库密切相关,提供了用于范围和迭代器的概念。这些概念在范围库中定义在std::ranges命名空间中。
#include <ranges>
#include <vector>
#include <iostream>
template <std::ranges::range R>
void print_range(const R& range) {
for (const auto& elem : range) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
print_range(v); // 合法
return 0;
}
其他工具支持
std::invocable
std::invocable用于检查一个对象是否可以用指定的参数调用。
#include <concepts>
#include <iostream>
void func(int, double) {}
template <typename F, typename... Args>
requires std::invocable<F, Args...>
void invoke(F f, Args... args) {
f(args...);
}
int main() {
invoke(func, 42, 3.14); // 合法
// invoke(func, 42); // 编译错误,参数数量不匹配
return 0;
}
std::regular
std::regular用于检查一个类型是否满足“常规类型”的要求,即它必须满足以下概念:std::copyable、std::movable、std::default_initializable、std::destructible、std::swappable。
#include <concepts>
#include <iostream>
template <std::regular T>
void check_regular(T) {
std::cout << "Type is regular." << std::endl;
}
int main() {
int x = 42;
check_regular(x); // 合法
return 0;
}
总结
C++20引入的<concepts>头文件提供了一系列强大的工具
,用于在模板编程中对类型进行精确的约束。这些概念不仅提高了代码的类型安全性,还增强了代码的可读性和可维护性。通过合理使用这些预定义的概念,开发者可以编写出更加健壮和高效的模板代码。希望本文对<concepts>头文件的详细介绍能够帮助读者更好地理解和应用这些新特性。