关于C++模板的基础用法可见我之前写的C++ template(模板)简易教程
本文所有代码均在C++11下编译。
SFINAE是个什么
替换失败并非错误 (Substitution failure is not an error, SFINAE)是指C++语言在模板参数匹配失败时不认为这是一个编译错误。—wikipedia
简单来说,SFINAE就是在模板实例化时如果生成的模板函数签名或者模板类存在错误,此特化将会被忽略,而不是抛出编译错误。
注意这里的存在错误,在模板实例化阶段编译器能够检查的只有函数签名,类成员,继承。在函数体里写的错误并不会被检查。
SFINAE可以做到对传入模板类型根据其行为特性选定不同的实现方案,经常被用于模板元编程中。但是今天我们并不讲模板元编程,仅仅谈SFINAE本身的扩展。
SFINAE并没有修改C++的世界,调用一个模板函数最终还是只能有一个实现(除非有更特化的重载),使用一个模板类最终还是只能有一个实现。SFINAE仅仅实在模板实例化阶段起作用,原先STL中那些只有一个实现的模板函数/类依然有效。
SFINAE极大的丰富了C++模板的扩展性,是知名的模板黑魔法的入门必修课。
一个简单的例子
我们实现一个应用了SFINAE规则的函数
template<class T,class funcT>
auto operator /(T&& a,funcT func)->decltype(func(forward<T>(a))){
return func(forward<T>(a));
}
使用例
auto i=vector<int>{12,1243,124};
cout << (i/[](vector<int>& x){return x.size();});
输出
3
此时如果我们使用常规的除法
124/23;
此函数实现会因为decltype
失败而被编译器自动忽略,从而调用原本的除法操作。
C++标准库对SFIANE的支持
template< bool B, class T = void >
struct enable_if;
enable_if,根据传入布尔值特化为int
或者T
,T
默认为void
经常用于根据编译期布尔常量隐藏一个函数重载或模板特化
读入Tuple
#1
template<size_t i,class ...Args>
auto operator>>(istream& is,const tuple<Args...> &r)->decltype(
typename enable_if<i==tuple_size<tuple<Args...>>::value>::type(1),is){
return is;
}
#2
template<size_t i=0,class ...Args>
auto operator>>(istream& is,const tuple<Args...> &r)->decltype(
typename enable_if<i!=tuple_size<tuple<Args...>>::value>::type(1),is){
is>>get<i>(r);
return operator>> <i+1>(is,r);
}
当传入tuple
大小等于模板参数i
时调用#1
实现,不等于i
时调用#2
实现,不指定模板参数i
时调用默认模板参数i=0
的#2
实现。
使用例
tuple<int,double,tuple<string,int>> a;
cin>>a;
cout << get<0>(a) << " " << get<1>(a) << " "
<< get<0>(get<2>(a)) << " "
<< get<1>(get<2>(a));
输入
214 125 asdf 1
输出
214 125 asdf 1
读入多维数组
template<class T,class ...Args>
struct Rtar{
T& a;const tuple<Args...> n;
Rtar(T& a,const tuple<Args...> n):a(a),n(n){}
};
template<class T,class ...Args>
Rtar<T,Args...> rtar(T &a,Args... rest){
return Rtar<T,Args...>(a,make_tuple(rest...));
}
template<size_t i,class T,class ...Args>
auto operator>>(istream& is,Rtar<T,Args...> r)->decltype(
typename enable_if<i==tuple_size<decltype(r.n)>::value>::type(1),is){
return is>>r.a;
}
template<size_t i=0,class T,class ...Args>
auto operator>>(istream& is,Rtar<T,Args...> r)->decltype(
typename enable_if<i!=tuple_size<decltype(r.n)>::value>::type(1),is){
using OT=typename decay<decltype(r.a)>::type::value_type;
for (size_t j=0;j<get<i>(r.n);j++){
OT w;
operator>> <i+1>(is,Rtar<OT,Args...>(w,r.n));
r.a.insert(r.a.end(),move(w));
}
return is;
}
Rtar
类作为中间件存储读入多维数组相关信息,Rtar::a
表示即将存入的多维数组,Rtar::n
表示维度数据
rtar
函数作为工厂函数封装Rtar
类存入读入相关信息,重载流输入操作符。
重载流输入操作符函数递归中会逐步循环Rtar::n
并同时解包多维数组为Rtar
类,在循环完毕后将会读入。
使用例
vector<vector<int>> v;
cin >> rtar(v,2,3);
for (auto &vi:v){
for (auto &vvi:vi)
cout << vvi << " ";
cout << endl;
}
输入
1 2 3
4 5 6
输出
1 2 3
4 5 6
实现简化版std::format!
优点:自动解包STL容器和tuple
缺陷:不能接受string
,容器必须不为空
template<class T>
auto __format(ostream &os,const char *c,const T& cv)->decltype(os<<cv,c+1){
os << cv;
while (*c!='}') c++;
return c+1;
}
template<size_t i,class T>
auto __format(ostream &os,const char *c,const T& cv)->decltype(
typename enable_if<i==tuple_size<T>::value>::type(1),c+1){return c;}
template<size_t i=0,class T>
auto __format(ostream &os,const char *c,const T& cv)->decltype(
typename enable_if<i!=tuple_size<T>::value>::type(1),c+1){
while (*c!='{') os << *c++;
c=__format(os,c,get<i>(cv));
return __format<i+1>(os,c,cv);
}
template<class T>
auto __format(ostream &os,const char *c,const T& cv)->decltype(begin(cv),c+1){
const char *ct=c+1;
size_t ic=0;
for (auto &i:cv){
const char *cc=c+1;
while (*cc!='{'){
if (*cc=='i') os << ic,cc++;
else os << *cc++;
}
cc=__format(os,cc,i);
while (*cc!='}') os << *cc++;
ct=cc;
ic++;
}
return ct+1;
}
void _format(ostream &os,const char *c){return ;}
template<class T,class ...Args>
void _format(ostream &os,const char *c,const T &a,Args&& ...rest){
while (*c!='{'&&*c!='\0') os<< *c++;
if (*c=='{') c=__format(os,c,a);
_format(os,c,forward<Args>(rest)...);
}
template<class ...Args>
string format(const char *c,Args&& ...rest){
ostringstream os;
_format(os,c,forward<Args>(rest)...);
return os.str();
}
template<class ...Args>
ostream& print(const char *c,Args&& ...rest){return _format(cout,c,forward<Args>(rest)...),cout;}
__format
为向os
输出单个元素,针对tuple
和容器运用SFINAE
规则分别应用特化。
_format
封装__fromat
,对多个传入参数应用__format
format
封装_format
,设置字符串输出流,实现返回string
print
封装_format
,设置默认输出流为cout
使用例
vector<vector<int>> v;
cin >> rtar(v,2,3);
print("{}:\n{{{} }\n}\n",v.size(),v);
其中{{{} }\n}
表示对传入变量v
进行俩次解包,第二次解包后每个元素之后输出空格,第一次解包后每个元素之后输出回车。
输入
1 2 3
4 5 6
输出
2:
1 2 3
4 5 6
benchmark
cin.tie(0);
ios::sync_with_stdio(false);
vector<vector<int>> v;
v.resize(10000,vi(10000,0));
for (auto &i:v) for (auto &j:i) j=rand();
{
clock_t beg=clock();
format("{{{} }\n}\n",v);
clock_t ed=clock();
cout << (ed-beg) << endl;
}
{
clock_t beg=clock();
ostringstream os;
for (auto &i:v){
for (auto &j:i){
os << j << " ";
}
os << endl;
}
clock_t ed=clock();
cout << (ed-beg) << endl;
}
自有format实现 | std::iostream | |
---|---|---|
无优化 | 7766169 | 5096623 |
-O2 | 7050722 | 4381781 |
-Ofast | 5797949 | 4360714 |