问题背景
想要实现一个append(C& contianer, T* ptr, size_t size)函数,作用是拷贝size个数组ptr中的元素到容器container中。为了防止container扩容引发的性能开销,希望如果container有reserve成员,先调用reserve分配好内存,如果没有就算了。
template <typename C, typename T>
void append(C& container, T* ptr, size_t size)
{
container.reserve(container.size() + size); // 有reserve的版本
for (size_t i = 0; i < size; ++i) {
container.push_back(ptr[i]);
}
}
template <typename C, typename T>
void append(C& container, T* ptr, size_t size)
{
// 无reserve的版本
for (size_t i = 0; i < size; ++i) {
container.push_back(ptr[i]);
}
}我们能不能像python一样,
template <typename C, typename T>
void append(C& container, T* ptr, size_t size)
{
if (has_reserve<C>::value) { // 实现一个trait,判断C有没有reserve成员
container.reserve(container.size() + size); // #1
}
for (size_t i = 0; i < size; ++i) {
container.push_back(ptr[i]);
}
}很遗憾不行,因为当类型C没有reserve成员时,#1处直接编译错误。
解决方案
#include <vector>
#include <iostream>
#include <list>
#include <type_traits>
using namespace std;
template <typename T, typename = void_t<>>
struct has_reserve : false_type {};
template <typename T>
struct has_reserve<T, void_t<decltype(declval<T&>().reserve(1U))>> : true_type {};
template <typename C, typename T>
void append(C& container, T* ptr, size_t size)
{
if constexpr (has_reserve<C>::value) { // #2
container.reserve(container.size() + size);
}
for (size_t i = 0; i < size; ++i) {
container.push_back(ptr[i]);
}
}
template <typename T>
void print(const T& container)
{
cout << __PRETTY_FUNCTION__ << ": ";
for (auto it = container.begin(); it != container.end(); ++it) {
cout << *it << ' ';
}
cout << endl;
}
int main()
{
cout << has_reserve<vector<int>>::value << endl;
cout << has_reserve<list<int>>::value << endl;
int a[] = {1,2,3,4,5};
list<int> lst;
vector<int> vec;
// append(lst, data(a), std::size(a));
// append(vec, data(a), std::size(a));
append(lst, a, 5);
append(vec, a, 5);
print(lst); // void print(const T&) [with T = std::__cxx11::list<int>]: 1 2 3 4 5
print(vec); // void print(const T&) [with T = std::vector<int>]: 1 2 3 4 5
return 0;
}这里#2处使用了if constexpr功能,由c++17引入(被吴咏炜称为c++17最喜爱的特性)。他的作用是,直接在编译期判断条件,判过了,if下面的语句才会被编译,否则直接跳过。
From AI,
SFINAE(Substitution Failure Is Not An Error)是C++模板编程中的一项关键规则,其核心在于:在函数模板的重载决议过程中,如果模板形参替换导致代码无效,编译器不会视其为错误,而是简单地将该模板特化从候选集中剔除。
工作原理
在模板实例化时,编译器会尝试用具体类型替换模板参数。如果替换后的类型或表达式在”立即语境”(如函数签名、返回类型)中产生非法代码,例如访问不存在的嵌套类型T::type,这次替换失败不会导致编译中止7。编译器会静默放弃这个特化版本,转而尝试其他可能的重载函数4。这一机制使得模板能够根据类型特征在不同实现间进行选择。
关键技术应用
std::enable_if是实现SFINAE的经典工具2。它通过模板特化来控制类型的定义:只有当第一个模板参数为true时,才定义嵌套的type类型3。例如,可以通过std::enable_if_t<std::is_integral_v<T>>来约束模板函数只接受整型参数。
例子
为所有整数类型实现一个特化,
template <typename T>
typename enable_if<!is_integral<T>::value, string>::type
Field2String(const T& field) { return to_string(field); }
// specialization for integers
template <typename T>
typename enable_if<is_integral<T>::value, string>::type
Field2String(T field) {
char buf[32] {0};
jeaiii::to_text_from_integer(buf, field);
return buf;
}为某几个指定的类型实现同一份特化,
// 当模板类型参数为A或B时,匹配这个特化。
template <typename T>
typename std::enable_if<std::is_same<T, A>::value || std::is_same<T, B>::value>::type
Foo(T* item) {
...
}