Concepts (C++20)


C++20 Concepts

Concepts 是 C++20 引入的核心特性,用于显式约束模板参数,提升代码可读性和错误提示。以下通过代码示例和原理分步骤解析其用法。


1. 基本概念
  • 目标:显式声明模板参数必须满足的条件。
  • 优势 :替代复杂的 SFINAE 和 enable_if,直接约束类型。

2. 定义 Concept

使用 concept 关键字定义约束条件。

示例 1:检查类型是否可打印
cpp 复制代码
#include <iostream>
#include <concepts>

template <typename T>
concept Printable = requires(std::ostream& os, T val) {
    { os << val } -> std::same_as<std::ostream&>;
};
  • 说明 :要求类型 T 必须支持 operator<< 输出到流。

3. 应用 Concept 到函数模板
示例 2:约束函数参数可打印
cpp 复制代码
void print(Printable auto val) {
    std::cout << val << std::endl;
}

int main() {
    print(42);       // 合法:int 可打印
    print("Hello");  // 合法:const char* 可打印
    // print(std::vector<int>{}); // 错误:vector 不满足 Printable
}

4. 标准库预定义 Concepts

使用 <concepts><ranges> 中的标准 Concepts。

示例 3:约束算术类型
cpp 复制代码
template <typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;

Arithmetic auto square(Arithmetic auto val) {
    return val * val;
}

int main() {
    square(5);      // 合法:int 是整数类型
    square(3.14);   // 合法:double 是浮点类型
    // square("5"); // 错误:const char* 不满足 Arithmetic
}

5. 组合 Concepts

使用逻辑运算符组合多个 Concepts。

示例 4:约束类型可加且结果匹配
cpp 复制代码
template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;
};

template <Addable T>
T add(T a, T b) {
    return a + b;
}

int main() {
    add(3, 4);      // 合法:int 满足 Addable
    // add("a", "b"); // 错误:const char* 不满足 Addable
}

6. 在类模板中使用 Concepts
示例 5:约束容器元素可默认构造
cpp 复制代码
#include <concepts>

template <std::default_initializable T>
class SafeContainer {
    T data;
public:
    SafeContainer() = default;
};

int main() {
    SafeContainer<int> c1;      // 合法:int 可默认构造
    // SafeContainer<std::unique_ptr<int>> c2; // 错误:unique_ptr 不可默认构造
}

7. 复杂约束与嵌套要求
示例 6:约束类型有 size() 方法且返回整型
cpp 复制代码
template <typename T>
concept HasSize = requires(T t) {
    { t.size() } -> std::integral;
};

void printSize(HasSize auto obj) {
    std::cout << obj.size() << std::endl;
}

struct Vec {
    size_t size() const { return 10; }
};

int main() {
    Vec v;
    printSize(v);           // 合法:Vec 有 size() 返回 size_t
    // printSize(42);       // 错误:int 没有 size()
}

8. 标准库 Concepts 示例

直接使用标准库中的 Concepts。

示例 7:使用 std::sort 约束可排序范围
cpp 复制代码
#include <vector>
#include <algorithm>
#include <ranges>

void sortAndPrint(std::ranges::sortable auto& range) {
    std::sort(range.begin(), range.end());
}

int main() {
    std::vector<int> vec{5, 3, 1, 4, 2};
    sortAndPrint(vec);      // 合法:vector 支持随机访问迭代器
    // std::list<int> lst{5, 3}; 
    // sortAndPrint(lst);  // 错误:list 迭代器不支持随机访问
}

9. 原理与编译器行为
  • 替换检查:编译器在模板实例化时验证 Concept 约束。
  • 错误提示:直接指出违反的约束条件,而非模板实例化失败。
  • 零开销:所有检查在编译期完成,无运行时负担。

总结

操作 语法示例 用途
定义 Concept template<typename T> concept C = ...; 声明模板参数的显式约束
函数模板约束 void func(C auto param) 限制参数类型满足 Concept
组合 Concepts C1 && C2 或 `C1
类模板约束 template<C T> class X {}; 约束类模板参数
标准库 Concepts std::integral<T>, std::sortable 快速实现通用约束

Concepts 显著提升了模板代码的可读性和健壮性,结合现代 C++ 特性(如简写模板),使得泛型编程更加直观高效。


多选题


题目 1:Concepts 的基本约束与函数模板重载

以下代码的输出是什么?

cpp 复制代码
#include <iostream>
#include <concepts>

template <typename T>
concept HasValue = requires(T t) { t.value; };

struct A { int value; };
struct B {};

void process(HasValue auto obj) { std::cout << "HasValue" << std::endl; }
void process(auto obj) { std::cout << "Generic" << std::endl; }

int main() {
    A a;
    B b;
    process(a); // 调用哪个版本?
    process(b); // 调用哪个版本?
    return 0;
}

A. HasValueGeneric

B. GenericGeneric

C. 编译失败,重载冲突

D. 运行时错误


题目 2:组合 Concepts 的逻辑

以下代码是否能编译通过?

cpp 复制代码
#include <concepts>

template <typename T>
concept Integral = std::integral<T>::value;

template <typename T>
concept Floating = std::floating_point<T>::value;

template <typename T>
concept Number = Integral<T> || Floating<T>;

template <Number T>
T add(T a, T b) { return a + b; }

int main() {
    auto x = add(3, 4);       // int
    auto y = add(3.5, 4.5);   // double
    auto z = add("a", "b");   // const char*
    return 0;
}

A. 编译成功

B. 编译失败,add("a", "b") 不满足 Number

C. 编译失败,Number 的约束定义错误

D. 运行时错误


题目 3:标准库 Concepts 与迭代器约束

以下代码的输出是什么?

cpp 复制代码
#include <vector>
#include <list>
#include <ranges>

template <typename T>
void checkRange(T&& range) {
    if constexpr (std::ranges::random_access_range<T>) {
        std::cout << "Random Access";
    } else if constexpr (std::ranges::bidirectional_range<T>) {
        std::cout << "Bidirectional";
    } else {
        std::cout << "Generic";
    }
}

int main() {
    std::vector<int> vec;
    std::list<int> lst;
    checkRange(vec);  // 输出什么?
    checkRange(lst);  // 输出什么?
    return 0;
}

A. Random AccessBidirectional

B. Random AccessGeneric

C. BidirectionalBidirectional

D. 编译失败,约束不合法


题目 4:嵌套要求与复杂约束

以下代码是否能编译通过?

cpp 复制代码
#include <concepts>

template <typename T>
concept ComplexCheck = requires(T t) {
    requires std::integral<decltype(t.value)>;
    { t.print() } -> std::same_as<void>;
};

struct Valid { int value; void print() {} };
struct Invalid1 { double value; void print() {} };
struct Invalid2 { int value; };

ComplexCheck auto process(auto obj) { obj.print(); }

int main() {
    process(Valid{});     // 合法?
    process(Invalid1{});  // 合法?
    process(Invalid2{});  // 合法?
    return 0;
}

A. 仅 Valid 合法

B. ValidInvalid1 合法

C. 所有调用均合法

D. 编译失败,ComplexCheck 定义错误


题目 5:Concepts 与模板特化的优先级

以下代码的输出是什么?

cpp 复制代码
#include <iostream>
#include <concepts>

template <typename T>
concept Large = sizeof(T) > 4;

void process(Large auto t) { std::cout << "Large"; }
void process(auto t) { std::cout << "Generic"; }

int main() {
    process(10);      // int(sizeof(int) = 4)
    process(10L);     // long(sizeof(long) = 8)
    return 0;
}

A. GenericLarge

B. LargeLarge

C. GenericGeneric

D. 编译失败,重载冲突


答案与解析


题目 1:Concepts 的基本约束与函数模板重载

答案:A
解析

  • process(HasValue auto) 的约束更严格,优先于无约束的 process(auto)
  • A 满足 HasValue(有 value 成员),调用第一个版本;B 不满足,调用第二个版本。
  • 选项 C 错误:Concepts 约束的函数和非约束函数可以合法重载,优先选择更特化的版本。

题目 2:组合 Concepts 的逻辑

答案:B
解析

  • Number 约束 T 必须是整数或浮点类型。
  • add("a", "b") 中的 const char* 不满足 Number,导致编译失败。
  • 选项 C 错误Number 定义正确,Integral<T> || Floating<T> 语法合法。

题目 3:标准库 Concepts 与迭代器约束

答案:A
解析

  • std::vector 的迭代器是随机访问迭代器,输出 Random Access
  • std::list 的迭代器是双向迭代器,输出 Bidirectional
  • 选项 D 错误:标准库 Concepts 的定义合法。

题目 4:嵌套要求与复杂约束

答案:A
解析

  • ComplexCheck 要求:
    1. t.value 的类型必须满足 std::integral
    2. t.print() 必须存在且返回 void
  • 只有 Valid 满足所有条件:
    • Invalid1value 类型是 double,不满足 std::integral
    • Invalid2 没有 print() 方法。

题目 5:Concepts 与模板特化的优先级

答案:A
解析

  • process(10)intsizeof 为 4,不满足 Large,调用 Generic
  • process(10L)longsizeof 为 8,满足 Large,调用 Large
  • 选项 D 错误:Concepts 约束的函数和非约束函数可以合法重载,无冲突。

总结

通过这组题目,可以深入理解 Concepts 的以下核心机制:

  1. 重载优先级:约束更严格的函数优先匹配。
  2. 组合逻辑 :通过 &&|| 组合多个 Concepts。
  3. 标准库集成 :如 std::ranges::random_access_range
  4. 嵌套要求 :使用 requires 子句细化约束条件。
  5. 编译时决策 :基于 sizeof 等编译期属性选择实现。
相关推荐
chendilincd2 天前
C++ 的史诗级进化:从C++98到C++20
java·c++·c++20
oioihoii17 天前
C++20 统一容器擦除:std::erase 和 std::erase_if
c++20
郭涤生18 天前
The whole book test_《C++20Get the details》_notes
开发语言·c++·笔记·c++20
郭涤生18 天前
Chapter 6: Concurrency in C++20_《C++20Get the details》_notes
开发语言·c++·笔记·c++20
__lost19 天前
C++20的协程简介
c++20·协程
BanyeBirth19 天前
C++2025年3月等级考试试题(部分)
c++20
点云SLAM20 天前
C++20新增内容
c++·算法·c++20·c++ 标准库
oioihoii21 天前
C++20 的新工具:std::midpoint 和 std::lerp
c++20
郭涤生23 天前
Chapter 1: Historical Context_《C++20Get the details》_notes
开发语言·c++20