エラーの原因は、おそらくstd::stringがis_containerを真にしてしまうことだと思います。そこで、コンテナかどうかに加えて、std::basic_stringではないかというのも条件に加えて見ると、成功するようです。
コード:
#include<iostream>
#include<vector>
#include<string>
#include<type_traits>
#include<utility>
#include<algorithm>
namespace my{
template<typename T>
struct identity{
typedef T type;
};
template<typename T>
class is_container{
template<typename U>
static std::true_type check1(U*u,decltype(u->begin())* = nullptr);
static std::false_type check1(...);
template<typename U>
static std::true_type check2(U*u,decltype(u->end())* = nullptr);
static std::false_type check2(...);
public:
static const bool value = identity<decltype(check1((T*)0))>::type::value && identity<decltype(check2((T*)0))>::type::value;
typedef typename std::integral_constant<bool,value>::type type;
};
template<typename T>
class is_string:public std::false_type{
};
template<typename CHART,typename TRAITST,typename ALLOCT>
class is_string<std::basic_string<CHART,TRAITST,ALLOCT> >:public std::true_type{
};
template<typename T>
typename std::enable_if<!my::is_container<T>::value||is_string<T>::value,std::ostream&>::type print(std::ostream& stream,const T& t){
return stream<<t;
}
template<typename T>
typename std::enable_if<my::is_container<T>::value&&!is_string<T>::value,std::ostream&>::type print(std::ostream& stream,const T& container){
stream<<"[";
std::for_each(container.begin(),container.end()
,[&stream](typename T::value_type const& value)->void{
print(stream,value)<<",";
});
return stream<<"]";
}
}
template<typename C>
typename std::enable_if<my::is_container<C>::value&&!my::is_string<C>::value,std::ostream&>::type operator<<(std::ostream& stream,const C& c){
return my::print(stream,c);
}
int main(){
std::vector<std::vector<int> > v;
for(int i=0; i < 3; i++){
v.push_back(std::vector<int>());
for(int j=0; j < 5; j++){
v.at(i).push_back(i*j);
}
}
std::cout<<v<<std::endl;
my::print(std::cout,std::string("test"));
}
自分のコードに修正を加えているのは、申し訳ないのですが環境が異なるからです(こちらはVC10です。)
SFINAE - Wikipedia(英語) というのは、テンプレートを実体化した際に、テンプレート実引数が原因でエラーが生じる場合、それをコンパイルエラーとしなくてもよい。というルールのことで、特に関数オーバーロードがかかわる場合が多いです(それが全てではありませんが…)
たとえば、次のようなテンプレート関数を定義したとします。
コード:
/*
ここでは、int型は4バイトあるものと仮定する,だから、このテンプレートはTがintならvalueは偽値を示す。
それと、std::stringはポインタ(これも4バイトと仮定)と文字列の長さ(unsigned intで保持していると仮定)の計8バイトと仮定する。
*/
template<typename T>
struct less_than_4{
static const bool value = sizeof(T) < 4;
};
//Tがiteratorという型を持っていなければエラーになる(はず)
template<typename T>
void print(T t,typename T::iterator* = 0){
std::cout<<"[T may be container or std::string] "<<t<<std::endl;
}//1番
//Tの型の大きさが4以上だと要素数0の配列型が生成されてしまいエラーになる(はず)
template<typename T>
void print(T t,char(*)[less_than_4<T>::value?1:0] = 0){
std::cout<<"[T may be small : sizeof(T)<4] "<<t<<std::endl;
}//2番
//どんな型でも受け付ける。
void print(...){
std::cout<<"[T may be sizeof(T)>=4 && not have type iterator!]"<<std::endl;
}//3番
で、これをこのように呼び出したとします。
コード:
int main(){
print('a');//呼び出しA
print(std::string("test"));//呼び出しB
print(100);//呼び出しC
}
print関数はオーバーロードされていますから、呼び出しの実引数からどの関数が呼ばれるかが決定されますが、そのプロセスは…
まず、呼び出しABC全てで、printという関数が呼ばれるので、コンパイラは呼ばれる関数の候補として1~3番のprint関数を考慮します。
まず、呼び出しAですが、まず、1番のprint関数はテンプレートなので、テンプレート引数Tをcharとして実体化します。すると、char::iteratorという存在しない型が生成されてしまうため、テンプレート引数の置き換えに失敗しますが、
これはコンパイルエラーにならず、print関数1番を呼ばれる関数の候補から外します。
次に、2番のprint関数もテンプレートなので、1と同様実体化します。すると、sizeof(char)==1で、less_than_4::valueは真になりますから、配列型は要素数1で不正になりません。よって、呼ばれる関数の候補に残ります。print関数3番はどのような引数でも取れるので、当然候補に残ります。
そして、候補に残った2番と3番のprint関数のうち、2番のほうがより当てはまる(申し訳ありません、いい表現が思いつきませんでした)ので呼ばれる関数は2番に決まる。ということになります。
呼び出しBは、1番で実体化すると、std::stringはiteratorという型を持ちますから、不正にならず、呼び出し関数の候補に残ります。2番で実体化すると、sizeof(std::string)==8で、配列型は要素数0となり不正な型となり、呼び出し関数の候補から外れます。3番は呼び出しAと同様です。
そして、候補に残った1番と3番のうち、呼び出しAと同じ理由で1番が選ばれることになります。
呼び出しCは、1番も2番も実体化すると失敗(int::iteratorなんて型は存在しないし、sizeof(int)==4より配列型は要素数0の不正な型になる)しますので、どっちも候補から外れ、残った3番が呼ばれる関数になります。
SFINAEは、テンプレートを実体化するとchar::iteratorという存在しない型や、char[0]という不正な型が生成されるときに、
これがエラーにならず、なかったことになる(つまり候補から外される)ときに起こっています。