关于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或者TT默认为void

经常用于根据编译期布尔常量隐藏一个函数重载或模板特化

读入Tuple

std::tuple - cppreference

#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