完美转发
c++11引入了一个新的模板函数——std::forward,用于实现完美转发,即强制参数以右值形式转发。为什么要引入这样一个模板?先来看一个例子:
#include <iostream>
class X {
};
void f(X &x) {
std::cout << "call f(X &x)" << std::endl;
}
void f(const X &x) {
std::cout << "call f(const X &x)" << std::endl;
}
void f(X &&x) {
std::cout << "call f(X &&x)" << std::endl;
}
void g(X &x) {
f(x);
}
void g(const X &x) {
f(x);
}
void g(X &&x) {
f(x);
}
int main(int argc, char **argv) {
X x;
const X cx;
g(x); //call f(X &x)
g(cx); //call f(const X &x)
g(X()); //call f(X &x)
g(std::move(x)); //call f(X &x)
}
当执行g(X())和g(std::move(x))时,g(X &&x)理想地情况下是调用f(X &&x),但实际上却调用了f(X &x)。之所以会这样,是因为C++编译过程中发生了引用折叠(关于这一概念,可以参考《C++ 新特性 | C++ 11 | std::forward、万能引用与完美转发》)。通过使用std::forward,可以消除引用折叠,提高参数传递效率,如下:
void g(X &&x) {
f(std::forward<X>(x));
}
template <typename T>
void g(T &&x) {
f(std::forward<T>(x));
}
std::forward的实现
template <typename T>
struct remove_reference {
typedef T type;
};
template <typename T>
struct remove_reference<T &> {
typedef T type;
};
template <typename T>
struct remove_reference<T &&> {
typedef T type;
};
template <typename T>
using remove_reference_t = typename remove_reference<T>::type;
template <typename T>
T &&forward(remove_reference_t<T> &x) {
return static_cast<T &&>(x);
}
template <typename T>
T &&forward(remove_reference_t<T> &&x) {
return static_cast<T &&>(x);
}
关于std::forward的几点问题:
- remove_reference_t就是一组特化模板,通过forward的应用来看,仅有第一个定义就可以了,后面两个完全多余,但要知道remove_reference_t并非仅为forward而生,看下面的例子:
X x;
auto &&x__ = x;
std::cout << std::is_same_v<decltype(x__), X> << std::endl; //0
std::cout << std::is_same_v<remove_reference_t<decltype(x__)>, X> << std::endl; //1
- forword强制参数以右值方式转发,例如下面的实现g(T &x)将会调用f(T &&x),所以对于forward不要乱用:
template<typename T>
void h(T &x) {
f(forward<T>(x));
}
引入enable_if 表达式
类的特殊成员函数也可以是模板,例如构造函数,但有时这可能会带来令人意外的结果,如下面的例子:
#include <iostream>
#include <utility>
#include <string>
class Person final {
public:
template <typename T>
explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
}
Person (const Person &other) : m_PersonName(other.m_PersonName) {
std::cout<<"COPY-CONSTRPerson’" << m_PersonName << "’\n";
}
Person (Person &&other) : m_PersonName(std::move(other.m_PersonName)) {
std::cout << "MOVE-CONSTR Person ’" << m_PersonName << "’\n";
}
private:
std::string m_PersonName;
};
int main(int argc, char **argv)
{
std::string s = "sname";
Person p1(s);
Person p2("tmp");
Person p3(p1); //ERROR: In instantiation of function template specialization 'Person::Person<Person &>' requested here
Person p4(std::move(p1));
return 0;
}
上面的源码看上去没有任何错误,但却无法通过编译,错误如下:
很明显Person p3(p1)没有按常理出牌——调用拷贝构造函数,而是调用了成员模板template<typename STR> Person(STR&& n)。之所以会这样,是因为成员模板的模板参数T被替换成Person &,变成Person(Persion &n),是一个比预定义拷贝构造函数更匹配的函数。试图解决这个问题,需要引入一种新的工具enable_if,新的源码如下:
//...
class Person final {
public:
template <typename T, typename U = typename std::enable_if<std::is_convertible<T, std::string>::value>::type>
explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
}
//...
};
//...
如果T可以被转换成std::string,这个定义会被扩展成template<typename STR, typename = void> Person(STR&& n);,否则,这个模板会被忽略。
简化enable_if表达式
- 使用using重命名
//...
template <typename T>
using enable_str_temlate = typename std::enable_if<std::is_convertible<T, std::string>::value>::type;
class Person final {
public:
template <typename T, typename U = enable_str_temlate<T>>
explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
}
//...
};
//...
- 使用requires(但支持该语法至少要到C++20)
//...
class Person final {
public:
template <typename T>
requires std::is_convertible<T, std::string>::value
explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
}
//...
};
//...
- 使用concept(但支持该语法至少要到C++20)
//...
template <typename T>
concept concept_str_template = std::is_convertible<T, std::string>::value;
class Person final {
public:
template <typename T>
requires concept_str_template<T>
explicit Person(T &&name) : m_PersonName(std::forward<T>(name)) {
std::cout << "TMPL-CONSTR for ’" << m_PersonName << "’\n";
}
//...
};
//...