C++20 中关于更佳编译器错误的概念

enter image description here

在 C++ 中,模板允许函数和类在不牺牲类型安全性的情况下操作不同的数据类型,从而实现泛型编程。模板使用 template 关键字定义,允许开发者编写可重用的、类型无关的代码,例如函数(例如,template max(T a, T b))或类(例如,std::vector),其中类型 T 在编译时指定。

从历史上看,C++ 语言往往会产生复杂的编译器错误消息。主要原因是模板元编程。C++ 模板功能强大但复杂。当模板代码中出现错误时,编译器会生成包含嵌套类型信息的冗长错误消息,通常涉及深度模板实例化。模板函数中的一个简单错误就可能生成一条跨越多行且类型名称晦涩难懂的消息。

让我们考虑一个例子。在 C++ 中,我们经常使用“标准模板库 (STL)”。它包含一个实用的动态数组模板:std::vector。vector 通过自动内存处理和灵活的大小调整来管理元素序列。与固定大小的数组不同,它可以在运行时通过诸如 push_back 追加元素或 pop_back 移除元素等操作进行增长或收缩。您几乎可以在 std::vector 中存储任何内容,但也有一些限制。例如,您的类型必须是可复制的。

让我们创建一个不可复制的 C++ 类型:

struct non_copyable {
 non_copyable ( )  =  default ;
 non_copyable ( const non_copyable & )  =  delete ;  // 无复制

} ; 如果我们尝试使用 non_copyable 创建一个空的 std::vector,它似乎可以工作:

std::vector<non_copyable> v;

然而,一旦你尝试实际使用 std::vector 实例,你可能会收到详细的错误消息。让我写一个向量化函数模板,它接受任意类型的单个值,并返回一个包含该值作为唯一元素的 std::vector。如果我尝试用我的 non_copyable 类型调用这个向量化函数模板,就会遇到麻烦:

template <typename type>

std::vector vectorize(type&& t) { return {t}; }

void g() { non_copyable m; // works: std::vector v; // fails: vectorize(m); }

它无法正常工作,仅仅是因为编译器试图复制一个 non_copyable 实例。这不是一个很难解决的错误,但编译器的错误信息却可能非常严重。例如,你可能会收到以下错误:

include/c++/16.0.0//bits/allocator.h:133:30: note: in instantiation of template class 'std::__new_allocator' requested here
  |     class allocator : public __allocator_base<_Tp>
  |                              ^

include/c++/16.0.0/ext/alloc_traits.h:46:47: note: in instantiation of template class 'std::allocator' requested here | template | ^ include/c++/16.0.0/bits/stl_vector.h:93:35: note: in instantiation of default argument for '__alloc_traits' required here | typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template | ^~~~~~~~~~~~~~~~~~~~~~ include/c++/16.0.0/bits/stl_vector.h:458:30: note: in instantiation of template class 'std::_Vector_base>' requested here | class vector : protected _Vector_base<_Tp, _Alloc> | ^ :50:5: note: in instantiation of template class 'std::vector' requested here | vectorize(m);

C++ 概念 (Concepts) 于 C++20 中引入,是一种用于定义和强制模板参数约束的编译时机制。开发者可以使用概念关键字指定类型必须满足的条件才能用于模板,例如必须具有某些操作、成员函数或继承自特定基类。概念可以通过及早捕获类型不匹配来改进错误消息,并通过记录意图来提高代码的表达能力。它们可以直接应用于模板声明(例如,template)。

在我们的例子中,我们可以定义一个可以在 std::vector 实例中使用的类型:我们要求该类型是可破坏的、可复制的和默认可构造的。

template <typename T>

concept vector_element = requires(T a, T b) { // Must be destructible requires std::destructible;

// Must be copy constructible
requires std::copy_constructible<T>;

// Must be copy assignable
requires std::assignable_from<T&, T>;

// Must be default constructible (for operations like resize)
requires std::default_initializable<T>;

}

C++20 中的概念是通用的。例如,如果我想要求一个实例可以与自身进行比较,我可能会定义以下概念:

template<typename T>

concept equality_comparable = requires(T a, T b) { { a == b } -> std::convertible_to; };

您可以类似地为支持“push_back”方法的 std::vector 等类型定义一个概念。

template<typename T>

concept pushable = requires(T a, typename T::value_type val) { { a.push_back(val) }; };

无论如何,现在让我用我新定义的vector_element概念编写一个新的vectorize函数:

template <vector_element type>

std::vector safe_vectorize(type&& t) { return {t}; }

这一次,当您编写以下错误代码时,您将收到更好的错误消息:

void g() {

non_copyable m; safe_vectorize(m); }

例如,您可能会收到以下错误:

error: no matching function for call to 'safe_vectorize' note: candidate template ignored: constraints not satisfied [with type = non_copyable &] note: because 'non_copyable &' does not satisfy 'vector_element'

C++ 模板虽然功能强大,能够支持通用且可复用的代码,但通常会导致复杂冗长的错误消息,尤其是在误用的情况下,例如 std::vector 和 non_copyable 示例中所示。C++20 概念的引入允许开发者显式地强制执行类型约束,从而实现更清晰、更简洁的错误诊断。通过使用像vector_element这样的概念,程序员可以尽早发现错误并提高代码的可读性。

无论是编译器供应商还是主要用户,C++20 的采用都十分出色。如果可以的话,我建议你尝试一下 C++ 的概念。

原文

评论