在开始学习SFINAE之前我们首先要了解的是:
template parameters 和 template arguments(针对C++17,不对C++11/14做过多考虑.).
(1) template parameters:
template < parameter-list > declaration
template parameters分为以下三种类型:
- a non-type template parameter/argument;
- a type template parameter/argument;
- a template template parameter/argument;
1), non-type template parameter
type name
type name = default //例如: template<std::size_t number = 10>
type ... name
auto name //since c++17
上面的 type 必须为一下类型:
1,integral types
2,nullptr
3,lvalue reference type(to a object or a funcion)
4,pointer type(to a object or a function)
5,pointer to member(a member object pointer or a member function pointer)
6,enumeration type
7,C++17开始支持用auto:
template<auto n> struct B { /* ... */ };
B<5> b1; // OK: non-type template parameter type is int
B<'a'> b2; // OK: non-type template parameter type is char
B<2.5> b3; // error: non-type template parameter type cannot be double
(2) type template parameter
typename name(optional) // 1)
class name(optional) // 2)
typename|class name(optional) = default // 3)
typename|class ... name(optional) // 4)
1), 一个 template paramter但是该parameter的名字是可选的(optional).
2), 同上只是 声明 template parameter的关键字可以用class.
3), 举个例子: template<typename T = int> 或者 template<typename = int>
4),一个template parameter package但是该package的名字是可选的(optional).
(2.5) template template parameter
template < parameter-list > typename(C++17)|class name(optional) //1)
template < parameter-list > typename(C++17)|class name(optional) = default //2)
template < parameter-list > typename(C++17)|class ... name(optional) //3) (since C++11)
1), 一个template template parameter带有一个可选的名字.
2),举个例子:
template<typename T>
struct Node{};
template<template<typename> class nodeType = Node>
void function()
{
nodeType<int> node;
}
3), 一个template template parameter package包的名字是可以选的.
(3) Template non-type arguments
1), non-type arguments可以在实例化object期间可以执行convert const expression.
demo 1:
template<const int* pci> struct X {};
int ai[10];
X<ai> xi; // ok: array to pointer conversion and cv-qualification conversion
struct Y {};
template<const Y& b> struct Z {};
Y y;
Z<y> z; // ok: no conversion
template<int (&pa)[5]> struct W {};
int b[5];
W<b> w; // ok: no conversion
void f(char);
void f(int);
template<void (*pf)(int)> struct A {};
A<&f> a; // ok: overload resolution selects f(int)
2)当non-type parameter为 reference/pointer的时候:
(1), 不能ref/point 一个class object的 non-static member data/基类,以及一个数组的成员
(2), 不能ref/point 一个临时的object
(3), 不能ref/point 一个 string literal(比如 "shihuamarryme")
(4), 不能ref/point typeid(type)的结果.
(5), 不能ref/point __func__;
demo 2:
template<typename T> class A { int x; }; // primary template
template<class T> class A<T*> { long x; }; // partial specialization
// class template with a template template parameter V
template<template<typename> class V> class C
{
V<int> y; // uses the primary template
V<int*> z; // uses the partial specialization
};
C<A> c; // c.y.x has type int, c.z.x has type long
demo 3:
template<typename T> struct eval; // primary template
template<template<typename, typename...> class TT, typename T1, typename... Rest>
struct eval<TT<T1, Rest...>> {}; // partial specialization of eval
template<typename T1> struct A;
template<typename T1, typename T2> struct B;
template<int N> struct C;
template<typename T1, int N> struct D;
template<typename T1, typename T2, int N = 17> struct E;
eval<A<int>> eA; // ok: matches partial specialization of eval
eval<B<int, float>> eB; // ok: matches partial specialization of eval
eval<C<17>> eC; // error: C does not match TT in partial specialization because
// TT's first parameter is a type template parameter,
// while 17 does not name a type
eval<D<int, 17>> eD; // error: D does not match TT in partial specialization
// because TT's second parameter is a type parameter pack,
// while 17 does not name a type
eval<E<int, float>> eE; // error: E does not match TT in partial specialization
// because E's third (default) parameter is a non-type
demo 4:
template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template <class ...Types> class C { /* ... */ };
template<template<class> class P> class X { /* ... */ };
X<A> xa; // OK
X<B> xb; // OK in C++14 after CWG 150
// Error earlier: not an exact match
X<C> xc; // OK in C++14 after CWG 150
// Error earlier: not an exact match
template<template<class ...> class Q> class Y { /* ... */ };
Y<A> ya; // OK
Y<B> yb; // OK
Y<C> yc; // OK
template<auto n> class D { /* ... */ }; // note: C++17
template<template<int> class R> class Z { /* ... */ };
Z<D> zd; // OK
template <int> struct SI { /* ... */ };
template <template <auto> class> void FA(); // note: C++17
FA<SI>(); // Error
SFINAE
1), 什么是SFINAE?
Substitution Failure Is Not An Error(替换失败不是一个错误).
2),什么是Expression(In C++)?
看这里: http://en.cppreference.com/w/cpp/language/expressions
3),什么是Expression SFINAE?
Expression SFINAE(只是属于SFINAE的一种,如果想查看所有的SFINAE的特性请参阅这里):
根据上面的总结为2点:
1, 无效的表达式用在模板(function/class/struct的)参数类型
2, 无效的表达式用在函数返回类型
demo1 for function-type
struct X {};
struct Y { Y(X){} }; // X is convertible to Y
template <class T>
auto f(T t1, T t2) -> decltype(t1 + t2); // overload #1
X f(Y, Y); // overload #2
X x1, x2;
X x3 = f(x1, x2); // deduction fails on #1 (expression x1+x2 is ill-formed)
// only #2 is in the overload set, and is called
demo2 for template parameter type
#include <iostream>
#include <type_traits>
#include <utility>
//注意这里在vs是有bug的要去gcc/clang且支持c++14
int* function(int* ptr)noexcept
{
return ptr;
}
template<typename ...>
using void_t = void;
template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
template<typename T>
using type = decltype(function(std::declval<T*>()));
template<typename Ty, typename = void_t<>>
struct Test : public std::false_type {};
template<typename Ty>//speciafization
struct Test<Ty, void_t<type<Ty>>> : public std::true_type { };
int main()
{
std::cout << std::boolalpha;
std::cout << Test<int>::value << std::endl;
return 0;
}
demo3 for template constructor
这个例子必须要借助SFINAE才能正确运行.
因此我们需要特别注意的是:
1, 构造函数尽量不要定义为泛化构造函数
#include <iostream>
#include <typeinfo>
class Person{
private:
std::string str;
//----------------------重点.
class Get{
private:
int idx;
public:
using value_type = std::string;
Get()=default;
~Get()=default;
constexpr Get(const int& index):idx(index){};
value_type operator()(const int& index) noexcept
{
return std::string("shihuawoaini");
}
operator value_type() noexcept
{
return (*this)(idx);
}
};
//--------------------------
public:
Person()=default;
template<typename T>
explicit Person(T&& t);//泛化构造函数!
explicit Person(int idx);
Person(const Person& p);
Person(Person&& p);
virtual ~Person()=default;
};
template<typename T>
Person::Person(T&& t)
:str(std::forward<T>(t))
{
std::cout<<"T-type"<<std::endl;
std::cout<<typeid(t).name()<<std::endl;
std::cout<<std::boolalpha<<std::is_same<const char (&)[13], T>::value<<std::endl;
}
template<typename T>
Person::Person(T&& t)
:str(std::forward<T>(t))
{
std::cout<<"T-type"<<std::endl;
std::cout<<typeid(t).name()<<std::endl;
std::cout<<std::boolalpha<<std::is_same<const char (&)[13], T>::value<<std::endl;
}
Person::Person(int idx)
:str(Get(idx))
{
std::cout<<"-----------"<<std::endl;
}
Person::Person(const Person& p)
:str(p.str)
{
//
}
Person::Person(Person&& p)
:str(std::move(p.str))
{
}
class SpecialPerson : public Person{
private:
std::string str_;
public:
SpecialPerson(const std::string& base, const std::string& inherit):Person(base),str_(inherit){}
virtual ~SpecialPerson()=default;
SpecialPerson(const SpecialPerson& sp)noexcept;
SpecialPerson(SpecialPerson&& sp)noexcept;
};
SpecialPerson::SpecialPerson(const SpecialPerson& sp)noexcept
:Person(sp),
str_(sp.str_)
{
//
}
SpecialPerson::SpecialPerson(SpecialPerson&& sp)noexcept
:Person(sp),
str_(std::move(sp.str_))
{
//
}
int main()
{
Person p1(std::string("shihuawoaini"));
Person p2("shihuawoaini");
//case 1:
Person p3(2.5); //这里我们如果传入进去参数2.5,会调用模板构造函数,进而造成构造失败.
//为什么呢?
//因为2.5是double类型, 那么这么一来double类型转到int类型需要一次类型转换,而模板构造函数不需要转换
//也就提供了更加精确的匹配.
//case 2:
Person p3(p2); //这里调用了构造函数而不是拷贝构造函数.
//case 3:
SpecialPerson sp1(std::string("shihuawoani"), std::string("marry me"));
SpecialPerson sp2(sp1);
//这里会调用基类中的构造函数而不是基类中的拷贝构造函数!!!!!!!!
//因为调用基类中的拷贝构造函数需要进行派生类向基类的转换.
//而调用基类向派生类的转换是不需要的,因此匹配度更佳.
const Person p4("shihuawoaini");
Person p5(p4); //OK调用了拷贝构造函数.
Person p6(5); //OK调用了接受int的构造函数.
return 0;
}
demo 4:
#include <iostream>
class Person{
private:
std::string str;
class Get{
private:
int idx;
public:
using value_type = std::string;
constexpr Get(const int& index):idx(index){}
value_type operator()(const int& index)noexcept
{
return std::string("shihuawoaini");
}
operator value_type()noexcept
{
return (*this)(idx);
}
};
public:
//注意这里的模板声明.
template<typename T, typename = typename std::enable_if<
!std::is_base_of<Person, typename std::decay<T>::type>::value
&&
!std::is_integral< typename std::remove_reference<T>::type >::value
&&
!std::is_same<double, typename std::decay<T>::type>::value
&&
!std::is_same<const char16_t*, typename std::decay<T>::type>::value //解决utf-8的问题.
>::type>
Person(T&& t);
Person(const int& index);
Person(const Person& p);
Person(Person&& p);
virtual ~Person()=default;
};
template<typename T, typename>
Person::Person(T&& t)
:str(std::forward<T>(t))
{
//
std::cout<<"template"<<std::endl;
}
Person::Person(const int& index)
:str(Get(index))
{
//
std::cout<<"number"<<std::endl;
}
Person::Person(const Person& p)
:str(p.str)
{
//
}
Person::Person(Person&& p)
:str(std::move(p.str))
{
//
}
class SpecialPerson : public Person{
private:
std::string str_;
public:
SpecialPerson(const std::string& base, const std::string& inherit):Person(base),str_(inherit){}
virtual ~SpecialPerson()=default;
SpecialPerson(const SpecialPerson& sp);
SpecialPerson(SpecialPerson&& sp);
};
SpecialPerson::SpecialPerson(const SpecialPerson& sp)
:Person(sp),
str_(sp.str_)
{
//
}
SpecialPerson::SpecialPerson(SpecialPerson&& sp)
:Person(sp),
str_(std::move(sp.str_))
{
//
}
int main()
{
Person p1("shihuawoaini");
Person p2(std::string("shihuawoaini"));
//Person p(u"shihuawoaini"); //这里的 (u"shihuawoaini")是const char16_t[13]类型. std::string并没有构造函数结构这样的参数.
//qeustion 1:
Person p3(p2);
//question 2:
Person p4(2);
Person p5(3.14);
//question 3:
SpecialPerson sp1(std::string("shihua"), std::string("MarryMe"));
SpecialPerson sp2(sp1);
return 0;
}
demo 5 for tags dispatch(标签分配)
#include <iostream>
#include <type_traits>
template<typename T>
void logAndAddImpl(T&& name, std::true_type)
{
std::cout<<"true_type"<<std::endl;
}
template<typename T>
void logAndAddImpl(T&& name, std::false_type)
{
std::cout<<"false_type"<<std::endl;
}
template<typename T>
void logAndAdd(T&& name)
{
logAndAddImpl( std::forward<T>(name), std::is_integral< typename std::remove_reference<T>::type>()); //注意这里.
//上面用的是std::is_integral<typename std::remove_reference<T>::type>());
//而不是std::is_integral<typename std::remove_reference<T>::type>::value)
}
int main()
{
std::string str("shihuawoaini");
logAndAdd(str);
logAndAdd(10);
return 0;
}