SFINAE
是 Substitution Failure Is Not An Error 的缩写,表示“替换失败不是一个错误”。指的是,当编译器遇到模板参数不符合要求的情况时,会跳过这个无法匹配的模板,继续寻找合适的模板。
C++中,如何判断对象呢,先来看下面代码:
#include "X:\Work\Share\CCode\CPlatform\Metafunction\_metafunction_out.h"
using namespace std;
using namespace lf;
struct Sample{ };
template <class T>
static char test(int T::*) {
std::cout << typeid(T).name() << "是对象。\n" << "\n";
return 0;
}
int main()
{
test<Sample>(0);
return 0;
}
输出结果:
但是,下面语句显然不行:
那么,现在加一个重载函数:
#include "X:\Work\Share\CCode\CPlatform\Metafunction\_metafunction_out.h"
using namespace std;
using namespace lf;
struct Sample
{
};
template <class T>
static char test(int T::*) {
std::cout << typeid(T).name() << "是对象。\n" << "\n";
return 0;
}
template <class T>
static int test(...){
std::cout << typeid(T).name() << "\n";
return 0;
}
int main()
{
test<Sample>(0);
test<int>(0);
return 0;
}
输出:
问题是,执行语句: test<Sample>(0); 编译器怎么知道要调用那个函数,因为两个模板
函数都可以,这里就是关键了:
test<T>(0),T是类时,两个函数都可以调用,但编译器在选择test函数的重载版本时,会优先考虑那些能够接受特定类型参数的函数。
现在,is_class来了:
/// <summary>
/// 判断类型 T 是否class,与 std::is_class 一样。
/// </summary>
/// <typeparam name="T"></typeparam>
/// 创建时间:2024-05-07 最后一修改时间:2024-05-07 已测试
template <class T>
class is_class{
public:
/*
在is_class类中,定义了一个静态常量value,它的值根据test<T>(0)的大小来确定。
如果test<T>(0)返回的是字符类型(即指向类的指针),则value的值为true,表示给
定的类型是类;否则,value的值为false,表示给定的类型不是类。
这段代码的作用是提供一个通用的方法来判断一个类型是否为类,可以在编译时
进行类型检查和条件编译。
如果T是一个类类型,编译器会选择接受指向类的指针的重载版本的test函数,这是因为C++模板的类型
推导机制。
在C++中,模板是一种特殊的工具,它允许程序员编写可以处理多种数据类型的代码。当使用模板时,编译
器会根据提供的模板参数来实例化特定的函数或类。在这个过程中,编译器会尝试匹配最佳的函数重载版本,
这一过程称为重载解析(overload resolution)。
对于is_class模板类中的两个test函数:
template <class U> static char test(int U::*);
template <class U> static int test(...);
第一个test函数接受一个指向类成员的指针作为参数,这意味着它只能接受一个类类型的参数。第二个
test函数是一个可变参数模板函数,可以接受任何类型的参数。
当T是一个类类型时,编译器在选择test函数的重载版本时,会优先考虑那些能够接受特定类型参数的函
数。因为第一个test函数要求参数必须是一个类的指针类型,而T恰好是一个类类型,所以这个条件得到
满足。由于这个重载版本的test函数更具体,更符合T是一个类类型的情况,编译器会优先选择它。
总的来说,编译器选择接受指向类的指针的重载版本的test函数,是因为这个重载版本与T作为类类型的
条件更加吻合,这是C++模板类型推导和重载解析的结果。
*/
//sizeof(test), 并不会调用函数fun,可以不用函数体
template <class U>
static char test(int U::*){}
//sizeof(test), 并不会调用函数fun,可以不用函数体
template <class U>
static int test(...){}
public:
/// <summary>
/// test<T>(0),T是类时,两个函数都可以调用,但编译器在选择test函数的重载版本
/// 时,会优先考虑那些能够接受特定类型参数的函数。sizoef(char) = 1, 而sizeof(int) = 4,
/// 当sizeof(test<T>(0))返回1时,说明调用的是static char test(int U::*),也就是
/// 说 T 是一个类类型。
/// </summary>
/// 创建时间:2024-05-07 最后一修改时间:2024-05-07 已测试
static const bool value = sizeof(test<T>(0)) == 1;
};
下面来说is_base_of:
核心知识:
假如 _Font 是 _Object的派生类,下面语句,有一句是不能编译的:
_Object o1 = _Font(); //可以,编译通过
_Font f = _Object(); //不可以,不能编译通过。
就是巧妙利用这点,这实际上是一个SFINAE:
匹配失败并不是一种错误(Substitution Failure Is Not An Error)
template < typename T1, typename T2>
struct is_same {
static const bool value = false;
};
template < typename T>
struct is_same<T, T> {
static const bool value = true;
};
/// <summary>
/// 判断类型 Derived 是否Base的派生类,与 std::is_base_of 一样。
/// 这个函数只能判断类,不能用于基本类型。
/// 例如:is_base_of<_Object,_DList<int>>::value,对于is_base_of<double,int>
/// 就无能为力了,这就要用到下面的is_base_of了。
/// </summary>
/// <typeparam name="Base">类型</typeparam>
/// <typeparam name="Derived">派生类</typeparam>
/// <typeparam name=""></typeparam>
/// 创建时间:2024-05-08 最后一修改时间:2024-05-08 已测试
template < typename Base, typename Derived, bool = (is_class<Base>::value && is_class<Derived>::value)>
class is_base_of {
/*
test(Conv(),0),有两个可重载函数可选,编译器先检查
test(Derived, T),如果Conv能强制类型转换成 Derived(把基类转换成派生类),
则匹配这个函数,说明 Derived确实是Base的派生类,否则匹配第二个函数
test(Base, int)
核心知识:
假如 _Font 是 _Object的派生类,下面语句,有一句是不能编译的:
_Object o1 = _Font(); //可以,编译通过
_Font f = _Object(); //不可以,不能编译通过。
就是巧妙利用这点,这实际上是一个SFINAE:
匹配失败并不是一种错误(Substitution Failure Is Not An Error)
*/
template < typename T>
static char test(Derived, T) {}; //{}函数体是多余的,可以不用。
static int test(Base, int) {};//{}函数体是多余的,可以不用。
struct Conv {
operator Derived() {}; //{}函数体是多余的,可以不用。
operator Base() const {}; //{}函数体是多余的,可以不用。
};
public:
static const bool value = sizeof(test(Conv(), 0)) == 1;
};
/// <summary>
/// 匹配下列式子:
/// is_base_of<int,_Object>
/// is_base_of<_Object,int>
/// is_base_of<dobule,int>
/// </summary>
/// <typeparam name="Base"></typeparam>
/// <typeparam name="Derived"></typeparam>
/// 创建时间:2024-05-08 最后一修改时间:2024-05-08 已测试
template < typename Base, typename Derived>
class is_base_of<Base, Derived, false> {
public:
static const bool value = is_same<Base, Derived>::value;
};
/// <summary>
/// 相同类型的匹配下列式子:
/// is_base_of<int,int>
/// is_base_of<_Object,_Object>
/// </summary>
/// <typeparam name="Base"></typeparam>
/// 创建时间:2024-05-08 最后一修改时间:2024-05-08 已测试
template < typename Base>
class is_base_of<Base, Base, true> {
public:
static const bool value = true;
};