C++20 函数模板

enter image description here

“If you think it's simple, then you have misunderstood the problem.” ― Bjarne Stroustrup

C++20 函数模板一个例子

这段C++代码是一个函数模板,其功能是打印传递给它的任意数量的字符串,并且每个字符串之间用空格分隔,最后以换行符结束。下面是对这段代码的逐行解释:

    // 定义了一个名为 print_strings 的函数模板。
// 它可以接受任意数量、任意类型的参数,只要这些类型能够转换为 std::string_view。
void print_strings(std::convertible_to<std::string_view> auto&& ...s)
{
    // 创建了一个 std::initializer_list 对象,该对象包含所有转换为 std::string_view 后的参数。
    // 这里使用了参数包展开,将所有参数 s... 展开到初始化列表中。
    for (auto v : std::initializer_list<std::string_view>{s...})
        // 在范围 for 循环中遍历初始化列表中的每个元素(即每个字符串视图),
        // 并使用 std::cout 将它们输出到标准输出。每个字符串后面都跟着一个空格。
        std::cout << v << " ";
    // 在所有字符串输出完毕后,输出一个换行符。
    std::cout << std::endl;
}

重点整理如下:

  • 函数模板:print_strings 是一个函数模板,允许你传入任意数量和类型的参数。

  • 约束自动类型推导:std::convertible_to 是C++20引入的概念,它表示函数参数必须能够被转换为 std::string_view 类型。

  • 通用引用:auto&& 表示这个函数可以接受任何类型的参数,包括 lvalue 和 rvalue 引用。

  • 参数包:...s 是一个参数包,代表函数可以接受数量不定的参数。

  • 展开参数包:在构造 std::initializer_list 时,参数包 s... 被展开,这样就可以接收多个参数并转换为 std::string_view 类型。

  • 范围 for 循环:使用范围 for 循环 (for (auto v : ...)) 来遍历所有字符串视图。

  • 输出:使用 std::cout 输出字符串,每个字符串后面紧跟一个空格,循环结束后输出一个换行符。

这个函数的设计让你能够方便地传入不同类型但可以转换为 std::string_view 的任意数量参数,例如 std::string 对象、字符串字面量等,并且会自动以空格分隔并在末尾加上换行符输出。

这个函数模板定义形式为什么没有了头部的 template <typenmae T>这种形式?

在C++20之前,模板函数通常需要显式指定模板参数,如 template<typename T>。不过,C++20引入了一种新的语法特性,叫作概念(Concepts),同时还允许了对于自动推导的模板参数使用auto关键字。

这段代码中的函数模板使用了C++20的概念和新的语法来约束可接受的类型:

void print_strings(std::convertible_to<std::string_view> auto&& ...s)

Concepts概念与关键字requires

在C++20中,requires关键字可以与概念(Concepts)一起使用,以进一步指定模板参数应满足的约束条件。requires表达式允许在模板定义中进行更精细的要求或者提供特定的类型检查。

在这里:

  • std::convertible_to是一个概念,用来约束传递给函数的参数必须能够转换成std::string_view。
  • auto&&表明函数可以接受任何类型的参数,包括左值和右值,并且会根据调用时传递的实参来推导参数的具体类型。
  • ...s表示函数可以接受数量不定的参数,也就是一个参数包。

通过这种方式,你无需显式地声明模板参数来定义函数模板。编译器将自动推导出每个参数的类型,并检查它们是否满足概念std::convertible_to所约定的要求。如果传递的参数不能被转换为std::string_view,编译器将在编译时报错。

concepts所谓的概念,关键字require是不是可以一起配合使用

下面是一个简单的示例,展示了如何结合使用概念(Concepts)和requires:

#include <concepts>
#include <iostream>

// 定义一个概念,要求类型 T 必须支持小于(<)比较操作。
template<typename T>
concept LessThanComparable = requires(T a, T b) {
    { a < b } -> std::convertible_to<bool>;
};

// 使用概念和 requires 约束函数模板参数,
// 要求 T 类型必须满足 LessThanComparable 概念。
template<typename T>
requires LessThanComparable<T>
void compare_and_print(T a, T b) {
    if (a < b) {
        std::cout << a << " is less than " << b << std::endl;
    } else {
        std::cout << a << " is not less than " << b << std::endl;
    }
}

int main() {
    compare_and_print(10, 20); // 输出: 10 is less than 20
    compare_and_print('a', 'b'); // 输出: a is less than b
    // compare_and_print("foo", "bar"); // 编译错误,因为 C 风格字符串不支持直接用 < 比较
    return 0;
}

在这个例子中:

  • 我们定义了一个名叫 LessThanComparable 的概念,它要求类型 T 必须支持 < 运算符,并且结果可以转换成 bool 类型。
  • 接着我们定义了一个函数模板 compare_and_print,通过在模板参数前使用 requires 关键字和 LessThanComparable 概念来约束参数类型。
  • 在 main 函数中,我们调用了 compare_and_print 函数,并传入了满足 LessThanComparable 概念的参数,如整数和字符。
  • 如果尝试传递不支持 < 操作的参数(例如注释掉的 C 风格字符串的比较),将会导致编译时错误。

requires 表达式还可以直接放在模板参数列表之后,甚至可以用于非模板函数或方法,为重载提供了基于约束的选择。这样的使用增加了代码的灵活性和表现力。

评论