将 logAndAdd
改为两个函数 logAndAddName
和 logAndAddNameIdx
。但是这样就无法适配 Person
类的构造函数。
回归C++98,使用传递左值常量引用类型来代替传递万能引用类型。不过这会带来效率损失。
将传递的形参从引用类型替换成值类型:
class Person {
public:
explicit Person(std::string n)
: name(std::move(n)) {}
explicit Person(int idx)
: name(nameFromIdx(idx)) {}
...
private:
std::string name;
};
这样不会带来效率的损失,也能让重载决议符合期望。
如果我们既不想放弃虫子啊,二不想放弃万能引用,哪有如何避免依万能引用类型进行重载呢:
先回顾下先前的代码:
std::multiset<std::string> names;
template<typename T>
void logAndAdd(T&& name)
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(std::forward<T>(name));
}
我们将它改为:
template<typename T>
void logAndAdd(T&& name)
{
logAndAddImpl(std::forward<T>(name),
std::is_integral<std::remove_reference_t<T>());
}
template <typename T>
void logAndAddImpl(T&& name, std::false_type)
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(std::forward<T>(name));
}
void logAndAddImpl(int idx, std::true_type)
{
logAndAdd(nameFromIdx(idx));
}
std::true_type
和 std::false_type
都是用来参与重载决议的标签。
上面的实现方法依然在部分场景(如完美转发构造函数)中无法达到预期效果。这样就需要 std::enable_if
将含有万能引用部分的函数模板被允许采用的条件砍掉一部分。
在我们讨论的情况下,仅在传递给完美转发构造函数的类型不是 Person
时才会启用该模板函数,这样就可以让复制构造函数等函数正常执行。
修改后的代码如下:
class Person {
public:
template<
typename T,
typename = std::enable_if_t<
!std::is_same_v<Person, std::decay_t<T>>
>
>
explicit Person(T&& n);
...
};
为了实现继承,需要修改如下:
class Person {
public:
template<
typename T,
typename = std::enable_if_t<
!std::is_base_of<Person, std::decay_t<T>>::value
>
>
explicit Person(T&& n);
...
};
最后,还需要加入索引功能:
class Person {
public:
template<
typename T,
typename = std::enable_if_t<
!std::is_base_of<Person, std::decay_t<T>>::value
&&
!std::is_integral<std::remove_reference_t<T>>::value
>
>
explicit Person(T&& n)
: name(std::forward<T>(n))
{...}
explicit Person(int idx)
: name(nameFromIdx(idx))
{...}
...
private:
std::string name;
};
为了保证传入完美转发构造函数的参数类型可以转换为 std::string
,需要添加静态断言,因为模板元的报错信息很难看懂:
class Person {
public:
template<
typename T,
typename = std::enable_if_t<
!std::is_base_of<Person, std::decay_t<T>>::value
&&
!std::is_integral<std::remove_reference_t<T>>::value
>
>
explicit Person(T&& n)
: name(std::forward<T>(n))
{
static_assert(
std::is_constructible<std::string, T>::value,
"Parameter n can't be used to construct a std::string"
);
...
}
...
};