# 如何使用 C++20 的 Concepts 优化模板函数的参数校验

在 C++20 中,Concepts(概念)提供了一种强大而优雅的方式来对模板参数进行约束。相比传统的 SFINAE 技术,Concepts 更加易读、可维护,并且能够在编译时更早地捕获错误。本文将从概念的基本语法开始,展示如何在实际项目中使用它们来提升模板函数的可读性与安全性,并给出一个完整的示例:一个通用的 add 函数,仅接受可加法(+)且可打印的类型。

1. 概念(Concept)概述

概念是一种在编译时对类型约束的语义声明。其核心作用是:

  • 约束模板参数:指定某个类型必须满足哪些属性或行为。
  • 提高错误诊断:当约束不满足时,编译器会给出更明确的错误信息。
  • 改进可读性:在函数签名中直接表达意图,而不必隐藏在 std::enable_ifdecltype 等技巧中。

C++20 通过关键字 concept 定义一个概念,例如:

template<typename T>
concept Arithmetic = std::is_arithmetic_v <T>;

2. 常见内置概念

C++20 标准库提供了大量实用概念,可直接使用:

概念 说明
std::integral 整数类型
std::floating_point 浮点类型
std::arithmetic 整数或浮点
std::ranges::range 范围类型
std::output_iterator 可写入的迭代器
std::input_iterator 可读取的迭代器

使用方法:

template<std::integral T>
T clamp(T value, T low, T high);

3. 自定义概念

自定义概念可以组合内置概念或使用表达式:

template<typename T>
concept Printable = requires(T a) {
    { std::cout << a } -> std::same_as<std::ostream&>;
};

template<typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::convertible_to <T>;
};

在上例中:

  • Printable 要求类型 T 可通过 operator<< 输出到 std::cout
  • Addable 要求类型 T 的加法运算返回可转换为 T 的值。

4. 用概念约束模板函数

4.1 传统 SFINAE 写法

template<typename T>
auto add(const T& a, const T& b) ->
    typename std::enable_if<std::is_arithmetic<T>::value, T>::type
{
    return a + b;
}

4.2 Concept 写法

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

Concept 写法更直观,错误信息也更友好。

5. 结合多个约束

C++20 允许使用 &&|| 组合概念。举例:

template<Addable T, Printable U>
void print_sum(const T& a, const T& b, const U& label) {
    std::cout << label << ": " << add(a, b) << '\n';
}

此函数仅在 T 可加且 U 可打印时才可实例化。

6. 完整示例

#include <iostream>
#include <type_traits>
#include <concepts>

// 1. 定义概念
template<typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::convertible_to <T>;
};

template<typename T>
concept Printable = requires(T a) {
    { std::cout << a } -> std::same_as<std::ostream&>;
};

// 2. 泛型加法函数
template<Addable T>
T add(const T& a, const T& b) {
    return a + b;
}

// 3. 打印加法结果
template<Addable T, Printable U>
void print_sum(const T& a, const T& b, const U& label) {
    std::cout << label << ": " << add(a, b) << '\n';
}

int main() {
    int x = 5, y = 10;
    double p = 3.14, q = 2.71;
    std::string lbl = "Integer Sum";

    print_sum(x, y, lbl);           // OK
    print_sum(p, q, "Double Sum");  // OK

    // 以下代码会在编译时报错,因为 std::vector <int> 不是可加的
    // std::vector <int> v1{1,2}, v2{3,4};
    // print_sum(v1, v2, "Vector Sum");
}

编译运行结果

Integer Sum: 15
Double Sum: 5.85

若尝试传入不满足 AddablePrintable 的类型,编译器会给出类似下面的错误信息:

error: no matching function for call to ‘print_sum(std::vector <int>&, std::vector<int>&, const char [12])’

7. 小结

  • Concepts 让模板参数约束更清晰、可维护。
  • 结合标准库的内置概念可以快速满足常见需求。
  • 自定义概念时,使用 requires 表达式检查语义行为。
  • 在函数签名中直接使用概念,比传统 SFINAE 更直观。

通过上述方法,你可以在项目中轻松引入概念,提升代码质量并减少潜在错误。祝你编码愉快!

发表评论