c++++20概念通过在编译时对模板参数施加语义约束,提升了泛型代码的可读性、可维护性和错误信息的清晰度。1. 定义概念使用concept关键字和requires表达式,明确类型需满足的条件,如printable或addable;2. 使用概念约束模板参数可通过requires子句、简写语法或auto参数结合概念实现,使代码更简洁直观;3. 概念优势体现在清晰的意图表达、友好的错误信息、更好的可读性、参与重载解析及可组合性,相较于sfinae和static_assert,其语义化更强、调试更易、适用更广。

C++20概念(concept)提供了一种强大且直观的方式,在编译时对模板参数施加语义约束。它彻底改变了我们编写泛型代码的方式,让模板错误信息变得前所未有的清晰,同时极大地提升了代码的可读性和可维护性,告别了SFINAE的晦涩与痛苦。

使用C++20概念来约束模板参数,核心在于定义一个
concept

1. 定义一个概念 (Concept)
立即学习“C++免费学习笔记(深入)”;
一个概念本质上是一组编译时要求,用于描述一个类型或一组类型必须满足的特性。我们使用
concept
requires

例如,我们想定义一个“可打印”的概念,即类型可以被
std::cout
#include <iostream>
#include <type_traits> // 用于 std::void_t 和其他类型特性
// 定义一个名为 'Printable' 的概念
template<typename T>
concept Printable = requires(T val) {
{ std::cout << val } -> std::ostream&amp;; // 要求 val 可以被输出到 std::cout,且返回 std::ostream&
};
// 或者一个更简单的,只要求能被加法操作
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>; // 要求 a+b 返回类型与 T 相同
};这里的
requires
std::cout << val
-> std::ostream&
std::ostream&
std::same_as<T>
T
2. 使用概念约束模板参数
定义了概念之后,我们就可以用它们来约束函数模板、类模板的参数了。C++20提供了几种非常简洁的语法。
a. requires
这是最通用的方式,直接在模板参数列表后添加
requires
// 使用 requires 子句约束函数模板
template<typename T>
requires Printable<T>
void print_value(T value) {
std::cout << "Value: " << value << std::endl;
}
// 约束类模板
template<typename T>
requires Addable<T> && Printable<T> // 组合多个概念
class MyContainer {
T data;
public:
MyContainer(T val) : data(val) {}
void print_sum(T other) {
print_value(data + other); // 内部调用也受益于概念
}
};b. 概念作为类型占位符(简写语法)
对于单个类型参数,可以直接将概念名称放在类型参数的位置,这让代码看起来更像普通的函数签名。
// Printable T 替代了 template<typename T> requires Printable<T>
void print_value_shorthand(Printable auto value) { // 注意这里是 auto,C++20允许在函数参数中使用 auto
std::cout << "Shorthand Value: " << value << std::endl;
}
// 对于模板参数列表,也可以这样写
template<Printable T> // 相当于 template<typename T> requires Printable<T>
void print_templated_value(T value) {
std::cout << "Templated Value: " << value << std::endl;
}c. auto
C++20允许函数参数直接使用
auto
// 接受任何 Printable 类型的参数
void process_printable(Printable auto item) {
std::cout << "Processing: " << item << std::endl;
}
// 接受两个 Addable 且 Printable 的参数
void process_addable_and_printable(Addable auto a, Addable auto b) {
Printable auto sum = a + b; // 即使是局部变量也可以使用概念约束 auto
std::cout << "Sum: " << sum << std::endl;
}示例代码:
#include <iostream>
#include <string>
#include <vector>
#include <type_traits> // For std::same_as
// 定义概念
template<typename T>
concept Printable = requires(T val) {
{ std::cout << val } -> std::ostream&;
};
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
// 使用概念约束函数模板
template<Printable T>
void print_item(T item) {
std::cout << "Item: " << item << std::endl;
}
// 组合概念
template<Addable T>
requires Printable<T>
void add_and_print(T a, T b) {
T sum = a + b;
std::cout << "Sum of " << a << " and " << b << " is: " << sum << std::endl;
}
// 使用 auto 结合概念
void process_generic(Printable auto val) {
std::cout << "Generic processing: " << val << std::endl;
}
int main() {
print_item(123); // OK, int 是 Printable
print_item("Hello Concept"); // OK, const char* 是 Printable
print_item(std::string("World")); // OK, std::string 是 Printable
add_and_print(10, 20); // OK, int 既 Addable 又 Printable
add_and_print(3.14, 2.71); // OK, double 既 Addable 又 Printable
process_generic(42);
process_generic("Another generic call");
// 下面这行会触发编译错误,因为 std::vector<int> 默认不是 Printable
// print_item(std::vector<int>{1, 2, 3});
// 错误信息会非常清晰,指出 std::vector<int> 不满足 Printable 概念的要求
// 同样,如果尝试对不满足 Addable 的类型调用 add_and_print
// add_and_print("Hello", "World"); // 编译错误,string 的 + 操作返回 string,但我们要求是 same_as<T> 且 string 的 + 不符合我们 Addable 概念的意图
// 实际上 std::string::operator+ 返回 std::string,但这里我们假设 T 是 std::string,那么就满足 same_as<T>
// 但如果 T 是 const char*,那就不满足了。
// 更严谨的 Addable 应该考虑返回类型可以不同,或者更明确。
return 0;
}通过这些方法,C++20概念让模板编程变得更加语义化、可读,并且编译器的诊断信息也变得更加友好和精确。这真的是一个巨大的进步。
我个人觉得,C++模板编程在C++11/14时代,就像是拥有超能力但被蒙住双眼。你可以写出极其泛化、高效的代码,但一旦出错,那个SFINAE(Substitution Failure Is Not An Error)带来的错误信息,简直是噩梦。一串串几百行的模板实例化失败报告,让你根本不知道问题出在哪里,哪个具体的需求没被满足。这不光是新手望而却步,连经验丰富的老兵也得挠头。
概念(Concepts)的出现,彻底改变了这种局面。它不再是依赖于编译器“碰运气”地尝试替换,而是直接在语言层面提供了一种机制,让你明确地表达“我这个模板参数需要满足哪些条件”。这就像是给模板函数或类加上了一份清晰的“使用说明书”,而且这份说明书是编译器可以理解并强制执行的。
它的“救星”之处体现在几个关键点:
std::enable_if
decltype
Printable T
requires HasMemberFunction<T>
X
Y
Z
&&
||
!
所以,与其说它是“救星”,不如说它是让C++模板编程从“玄学”走向“科学”的关键一步。它让我们能更自信、更高效地编写和维护复杂的泛型代码。
编写高效且富有表现力的C++20概念,不仅仅是学会语法,更重要的是掌握其背后的设计哲学和一些最佳实践。这就像写一篇好文章,不仅要用对词,还要结构清晰,观点明确。
聚焦原子性与可组合性:
HasBeginEnd
begin()
end()
IsDereferenceable
IsIncrementable
&&
||
!
Range
HasBeginEnd && IsDereferenceable
善用requires
requires
noexcept
{ expr }expr
{ expr } -> ReturnType;expr
ReturnType
-> std::same_as<ReturnType>;
noexcept
{ expr } noexcept;expr
noexcept
requires
requires
// 示例:一个可迭代且其元素可打印的范围
template<typename T>
concept Iterable = requires(T t) {
{ t.begin() } -> std::input_or_output_iterator; // C++20的迭代器概念
{ t.end() } -> std::sentinel_for<decltype(t.begin())>;
};
template<typename T>
concept PrintableRange = Iterable<T> && requires(T t) {
requires Printable<typename std::iterator_traits<decltype(t.begin())>::value_type>;
};命名清晰、语义化:
Printable
Addable
CallableWithArgs
Iterator
Is
Has
Can
IsCopyConstructible
HasSizeMethod
考虑标准库概念:
<concepts>
std::same_as
std::convertible_to
std::integral
std::range
std::iterator
避免过度约束:
operator<<
编写高效且富有表现力的概念,是一个不断学习和实践的过程。它要求你对类型系统和模板机制有深入的理解,同时也要有清晰的逻辑思维来分解和组合需求。
static_assert
C++20概念、传统SFINAE(Substitution Failure Is Not An Error)以及
static_assert
1. 传统SFINAE (Substitution Failure Is Not An Error)
std::enable_if
decltype
std::void_t
typename
2. static_assert
static_assert
false
static_assert(condition, "error message");
static_assert
static_assert
static_assert
以上就是C++20概念(concept)怎么用 约束模板参数新语法详解的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号