重载转发引用的替代方案¶
上述问题的最直接解决方案是,不使用重载。其次是使用 C++98 的做法,不使用转发引用。
class A {
public:
template<typename T>
explicit A(const T& n) : s(n) {}
private:
std::string s;
};
A a("hi");
A b(a); //@ OK
直接按值传递也是一种简单的方式,而且解决了之前的问题:
std::string makeString(int n)
{
return std::string("hi");
}
class A {
public:
explicit A(std::string n) : s(std::move(n)) {}
explicit A(int n) : s(makeString(n)) {}
private:
std::string s;
};
unsigned i = 1;
A a(i); //@ OK,调用int版本的构造函数
不过上述方法实际上是规避了使用转发引用,下面是几种允许转发引用的重载方法:
标签分派¶
标签分派的思路是,额外引入一个参数来打破转发引用的万能匹配:
std::vector<std::string> v;
template<typename T>
void g(T&& s, std::false_type)
{
v.emplace_back(std::forward<T>(s));
}
std::string makeString(int n)
{
return std::string("hi");
}
void g(int n, std::true_type)
{
v.emplace_back(makeString(n));
}
template<typename T>
void f(T&& s)
{
g(std::forward<T>(s), std::is_integral<std::remove_reference_t<T>>());
}
unsigned i = 1;
f(i); //@ OK:调用int版本
f("hi");
使用std::enable_if在特定条件下禁用模板¶
标签分派用在构造函数上不太方便,这时可以使用 std::enable_if,它可以强制编译器在满足特定条件时禁用模板:
class A {
public:
template<typename T, typename =
std::enable_if_t<!std::is_same_v<A, std::decay_t<T>>>>
explicit A(T&& n) {}
private:
std::string s;
};
但这只是在参数具有和类相同的类型时禁用模板,派生类调用基类的构造函数时,派生类和基类也是不同类型,不会禁用模板,因此还需要使用 std::is_base_of:
class A {
public:
template<typename T, typename =
std::enable_if_t<!std::is_base_of_v<A, std::decay_t<T>>>>
explicit A(T&& n) {}
private:
std::string s;
};
class B : public A {
public:
B(const B& rhs) : A(rhs) {} //@ OK:不再调用模板
B(B&& rhs) : A(std::move(rhs)) noexcept {} //@ OK:不再调用模板
};
接着在参数为整型时禁用模板,即可解决之前的所有问题:
std::string makeString(int n)
{
return std::string("hi");
}
class A {
public:
template<typename T, typename =
std::enable_if_t<!std::is_base_of_v<A, std::decay_t<T>>
&& !std::is_integral_v<std::remove_reference_t<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(1); //@ OK:调用int版本的构造函数
A b("hi"); //@ OK
A c(b); //@ OK
为了更方便调试,可以用 static_assert 预设错误信息,这个错误信息将在不满足预设条件时出现在诊断信息中:
std::string makeString(int n)
{
return std::string("hi");
}
class A {
public:
template<typename T, typename =
std::enable_if_t<!std::is_base_of_v<A, std::decay_t<T>>
&& !std::is_integral_v<std::remove_reference_t<T>>>>
explicit A(T&& n) : s(std::forward<T>(n))
{
static_assert(std::is_constructible_v<std::string, T>,
"Parameter n can't be used to construct a std::string");
}
explicit A(int n) : s(makeString(n)) {}
private:
std::string s;
};