避免重载使用转发引用的函数

如果函数参数接受左值引用,则传入右值时执行的仍是拷贝。

std::vector<std::string> v;

void f(const std::string& s)
{
	v.emplace_back(s);
}

int main()
{
	//@ 传入右值,执行的依然是拷贝
	f(std::string("hi"));
	f("hi");
}

让函数接受转发引用即可解决此问题。

std::vector<std::string> v;

template<typename T>
void f(T&& s)
{
	v.emplace_back(std::forward<T>(s));
}
//@ 现在传入右值时变为移动操作
f(std::string("hi"));
f("hi");

但如果重载这个转发引用版本的函数,就会导致新的问题:

std::vector<std::string> v;

template<typename T>
void f(T&& s)
{
    v.emplace_back(std::forward<T>(s));
}

std::string makeString(int n)
{
    return std::string("hi");
}

void f(int n) //@ 新的重载函数
{
    v.emplace_back(makeString(n));
}

//@ 之前的调用仍然正常
f(std::string("hi"));
f("hi");
//@ 对于重载版本的调用也没问题
f(1); //@ 调用重载版本
//@ 但对于非int(即使能转换到int)参数就会出现问题
unsigned i = 1;
f(i); //@ 转发引用是比int更精确的匹配
//@ 为std::vector<std::string>传入short,用short构造std::string导致错误

转发引用几乎可以匹配任何类型,因此应该避免对其重载。此外,如果在构造函数中使用转发引用,会导致拷贝构造函数不能被正确匹配。

std::string makeString(int n)
{
    return std::string("hi");
}

class A {
public:
    A() = default;
    template<typename T>
    explicit A(T&& n) : s(std::forward<T>(n)) {}

    explicit A(int n) : s(makeString(n)) {}
private:
    std::string s;
};

unsigned i = 1;
A a(i); //@ 错误:调用模板而出错,但还有一个更大的问题
A b("hi"); //@ OK
A c(b); //@ 错误:调用的仍是模板,用A初始化std::string出错

模板构造函数不会阻止合成拷贝和移动构造函数(会阻止合成默认构造函数),上述问题的实际情况如下:

class A {
public:
    template<typename T>
    explicit A(T&& n) : s(std::forward<T>(n)) {}

    A(const A& rhs) = default;
    A(A&& rhs) = default;
private:
    std::string s;
};

A a("hi"); //@ OK
A b(a); //@ 错误:调用的仍是模板,用A初始化std::string出错
//@ 传入的是A&,T&&比const A&更匹配
const A c("hi");
A d(c); //@ OK

上述问题在继承中会变得更为复杂,如果派生类的拷贝和移动操作调用基类的构造函数,同样会匹配到使用了转发引用的模板,从而导致编译错误。