SFINAE :关于is_class,is_base_of,C++编译器的魔法器,如何实现,is_class,is_base_of。

6 篇文章 0 订阅
3 篇文章 0 订阅

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;
	
};

好了,我们来测试一下例子:

把鼠标放在value上,你可以看到:

这时候程序都还没运行,就已知结果,说明这些都是在编译期

前就已计算出结果来了,这也是C++模板元函数的妙用。

结论:C++编译器就像一个魔法器,很多只有你想不到的,

没有做不到的。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值