C++20中的`<concepts>`头文件详解

199 阅读4分钟

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_integralstd::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::copyablestd::movablestd::default_initializablestd::destructiblestd::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>头文件的详细介绍能够帮助读者更好地理解和应用这些新特性。