——《C++Templates》
完美转发(Perfect Forwarding)
std::forward
完美转发(perfect forwarding)是指在函数模板中通过保留传递给模板的参数的值类别(左值或右值)来转发它们,从而将参数完整地传递给另一个函数。完美转发的主要目的是在不丢失参数的值类别的情况下,将参数传递给其他函数,以保持传递参数的原始形式;
其一般和移动语义相结合,用于右值类型的传递,关键字为forward
;
#include <iostream>
#include <utility>
void func(int&& a) {
std::cout << "This is int&&." << std::endl;
std::cout << a << std::endl;
}
void func(int& a) {
std::cout << "This is int&." << std::endl;
std::cout << a << std::endl;
}
int main(void) {
int&& a = 1;
func(a);
std::cout << "***************" << std::endl;
func(std::forward<int>(a));
return 0;
}
这里如果不使用std::forward声明,参数a传递给func时会自动退化成为左值引用,关键字std::forward用于保持其右值属性;
std::move
move关键字用于移动语义的实现,其用于将一个左值类型转换成一个右值类型,所以我们也可以使用move实现对右值重载函数的调用:
func(std::move(a));
特殊成员函数模板
无模板的成员函数实例如下:
#include <iostream>
#include <string>
class Person {
private:
std::string _name;
public:
Person(const std::string& name)
:_name(name)
{
std::cout << "Person constructor(left_ref) function std::string." << std::endl;
}
Person(std::string&& name)
:_name(name)
{
std::cout << "Person constructor(right_ref) function std::string." << std::endl;
}
Person(const Person& p)
:_name(p._name)
{
std::cout << "Person(const Person&) left_ref constructor." << std::endl;
}
Person(Person&& p) noexcept
:_name(p._name)
{
std::cout << "Person(const Person&) right_ref constructor." << std::endl;
}
~Person() {
std::cout << "~Person()" << std::endl;
}
};
int main(void) {
Person p(std::string("a"));
Person copy_p(p);
Person move_p(std::move(p));
return 0;
}
第一个实例,使用了std::string的构造函数的返回值,属于右值类型,所以匹配右值引用调用;
第二个实例,由于p是一个左值类型,所以调用左值引用的构造函数;
第三个实例,使用了std::move,将左值类型转换成右值类型,调用右值构造;
现在对该类进行模板化改造:
#include <iostream>
#include <string>
#include <utility>
#include <type_traits>
class Person {
private:
std::string _name;
public:
template<typename T>
Person(T&& name)
:_name(std::forward<decltype(name)>(name))
{
std::cout << "Person constructor(right_ref) function std::string." << std::endl;
}
Person(const Person& p)
:_name(p._name)
{
std::cout << "Person(const Person&) left_ref constructor." << std::endl;
}
Person(Person&& p) noexcept
:_name(std::move(p._name))
{
std::cout << "Person(const Person&) right_ref constructor." << std::endl;
}
~Person() {
std::cout << "~Person()" << std::endl;
}
};
int main(void) {
Person p(std::string("a"));
Person copy_p(p);
Person move_p(std::move(p));
return 0;
}
这里删除了类的左值引用,并声明其右值引用为函数模板,当我们再次调用时,line-38这个拷贝构造函数会报错,给出的理由是“相较于编译期默认给出的左值拷贝构造函数,模板右值引用构造函数更为匹配,所以发生了函数调用错误”;
enable_if<>用于禁用模板的生成
enable_if的使用
为了解决这个问题,引入了一个辅助模板enable_if,用以对是否生成一个模板实例进行条件判断:
#include <iostream>
#include <type_traits>
template<typename T>
std::enable_if<std::is_same<T, int>::value>::type func() {
std::cout << "success" << std::endl;
}
int main(void) {
func<int>();
return 0;
}
这里进行了一个条件判断,只有当给定的类型为int时,才会初始化一个func函数实体,从而实现对模板生成的限制;
enable_if用于解决左值构造函数报错
对于之前的因函数匹配的问题而导致的函数崩溃,我们可以使用enable_if对其进行修改:
template<typename T, typename = std::enable_if<std::is_convertible<T, std::string>::value>::type>
Person(T&& name)
:_name(std::forward<decltype(name)>(name))
{
std::cout << "Person constructor(right_ref) function std::string." << std::endl;
}
对右值引用构造模板进行改造,加入生成实例条件,这里引入了一个标准类型特征——std::convertible,
用于判断两个类型是否可以进行转换;
在此处判断了T类型是否可以转换成std::string类型,并依此作为是否生成一个模板实例的标准;
如此,便可以避免因参数的匹配问题而导致的错误;
使用概念简化enable_if的表达式
在C++20中,引入了新特性——概念,用于约束模板的生成;
其基本语法为:
#include <iostream>
#include <concepts>
template<typename T>
requires std::is_same<int, T>::value
void func() {
std::cout << "success" << std::endl;
}
int main(void) {
func<int>();
func<double>();
return 0;
}
这里使用了概念的关键字requires,用于实现和enable_if类似的模板生成条件判断操作;
所以,我们可以通过使用C++20的新特性——概念,对enable_if进行表达式的优化;
相较于enable_if来说,requires关键字有以下优点:
- 语法更简洁清晰:
requires
表达式的语法更加直观和简洁,它直接放在函数模板或者类模板的定义之后,使得约束条件更加直观。 - 直接支持多个约束:
requires
表达式可以直接列出多个约束条件,而不需要额外的模板参数。这使得代码更加简洁和易于理解。 - 更好的错误消息:使用
requires
表达式的代码在编译期间出现错误时,通常会产生更加清晰和直观的错误消息,因为错误通常与约束条件直接相关。 - 可读性更高:
requires
表达式将约束条件直接放在模板声明的位置,使得模板的约束条件更容易理解和维护。 - 不再需要额外的模板参数:相较于
enable_if
,requires
表达式不再需要额外的模板参数来激活或禁用模板的实例化,从而使得模板的使用更加简单。