在 C++ 中,模板允许函数和类在不牺牲类型安全的情况下操作不同的数据类型,从而实现泛型编程。使用 template 关键字定义,它们允许开发人员编写可重用的、与类型无关的代码,例如函数(例如,模板 T max(T a, T b))或类(例如,std::vector),其中类型 T 在编译时指定。
从历史上看,C++ 语言往往会产生复杂的编译器错误消息。罪魁祸首是模板元编程。 C++ 模板功能强大但很复杂。当模板代码中出现错误时,编译器会生成带有嵌套类型信息的长而详细的消息,通常涉及深层模板实例化。模板函数中的一个简单错误可能会产生一条跨越多行且类型名称模糊的消息。
让我们考虑一个例子。在C++中,我们经常使用“标准模板库(STL)”。它包括一个有用的动态数组模板:std::vector。向量通过自动内存处理和灵活的大小调整来管理元素序列。与固定大小的数组不同,它可以在运行时通过诸如push_back来追加元素或pop_back来删除元素之类的操作来增大或缩小。您可以在 std::vector 中存储几乎任何内容,但有一些限制。例如,您的类型必须是可复制的。
让我们创建一个不可复制的 C++ 类型:
结构不可复制{ non_copyable ( ) =默认值; non_copyable ( const non_copyable & ) =删除; // 没有副本 } ;
如果我们尝试使用 non_copyable 创建一个空的 std::vector ,它似乎可以工作:
std ::向量< non_copyable > v ;
但是,一旦您尝试实际使用 std::vector 实例,您可能会收到详细的错误消息。让我编写一个向量化函数模板,它接受任何类型的单个值,并返回一个包含该值作为其唯一元素的 std::vector。如果我尝试使用 non_copyable 类型调用此向量化函数模板,则会遇到麻烦:
模板<类型名称类型> std ::向量<类型>向量化(类型& & t ) { 返回{ t } ; } 无效g ( ) { 不可复制的 m ; // 工作: std ::向量< non_copyable > v ; // 失败: 向量化( m ) ; }
它不起作用只是因为编译器尝试复制 non_copyable 实例。这不是一个困难的错误。然而,编译器错误消息可能是史诗般的。例如,您可能会收到以下错误:
include/c++/16.0.0//bits/allocator.h:133:30: 注意:在此处请求的模板类“std::__new_allocator”的实例化中 |类分配器:public __allocator_base<_Tp> | ^ include/c++/16.0.0/ext/alloc_traits.h:46:47:注意:在此处请求的模板类“std::allocator”的实例化中 |模板 | ^ include/c++/16.0.0/bits/stl_vector.h:93:35:注意:在此处需要 '__alloc_traits<std::allocator>' 的默认参数实例化 | typedef 类型名 __gnu_cxx::__alloc_traits<_Alloc>::template | ^~~~~~~~~~~~~~~~~~~~~~ include/c++/16.0.0/bits/stl_vector.h:458:30:注意:在此处请求的模板类“std::_Vector_base>”的实例化中 |向量类:protected _Vector_base<_Tp, _Alloc> | ^ :50:5: 注意:在此处请求的模板类“std::vector”的实例化中 |向量化(m);
C++20 中引入的 C++ 概念是一种用于定义和强制模板参数约束的编译时机制。使用 Concept 关键字,开发人员可以指定类型必须满足才能与模板一起使用的要求,例如具有某些操作、成员函数或从特定基类继承。概念通过尽早捕获类型不匹配来改进错误消息,并通过记录意图使代码更具表现力。它们可以直接应用于模板声明(例如,模板)。
在我们的例子中,我们可以定义一个可以在 std::vector 实例中使用的类型:我们要求该类型是可破坏、可复制和默认可构造的。
模板<类型名T > 概念向量元素=需要( T a , T b ) { // 必须是可破坏的 需要std :: destructible < T > ; // 必须是可复制构造的 需要std :: copy_constructible <T> ; // 必须是可复制分配的 需要std :: assignable_from < T & , T > ; // 必须是默认可构造的(对于调整大小等操作) 需要std :: default_initialized <T> ; } ;
C++20 中的概念是通用的。例如,如果我想要求一个实例可以与其自身进行比较,我可能会定义以下概念:
模板<类型名T > 概念equal_comparable =要求( T a , T b ) { { a == b } - > std :: convertible_to <bool> ; } ;
您可以类似地为支持“push_back”方法的 std::vector 类型定义一个概念。
模板<类型名T > 概念可推送=需要( T a , typename T :: value_type val ) { {一个.推回( val ) } ; } ;
无论如何,现在让我用新定义的向量元素概念编写一个新的向量化函数:
模板<向量元素类型> std :: vector <类型> safe_vectorize (类型&& t ) { 返回{ t } ; }
这次,当您编写以下错误代码时,您将得到更好的错误消息:
无效g ( ) { 不可复制的 m ; 安全向量化(米) ; }
例如,您可能会收到以下错误:
错误:没有匹配的函数可用于调用“safe_vectorize” 注意:候选模板被忽略:不满足约束[with type = non_copyable &] 注意:因为 'non_copyable &' 不满足 'vector_element'
C++ 模板虽然对于实现通用和可重用代码功能强大,但通常会导致复杂且冗长的错误消息,特别是在误用时,如 std::vector 和 non_copyable 示例所示。 C++20 概念的引入允许开发人员显式强制执行类型约束,从而实现更清晰、更简洁的错误诊断。通过使用向量元素等概念,程序员可以及早发现错误并提高代码可读性。
无论是编译器供应商还是主要用户,C++20 的采用都非常出色。如果可以的话,我建议您尝试一下 C++ 概念。
原文: https://lemire.me/blog/2025/05/03/c20-concepts-for-nicer-compiler-errors/