11111111111

C++ 设计模式之 SFINAE:用来检测类中是否有某个成员函数 

2022-01-07 11:55

针对类中特定成员函数的检测其实在工作中也可能用到。C++中可以用SFINAE 技巧达到这个目的。

SFINAE是 Substitution Failure Is Not An Error的缩写,直译为:匹配失败不是错误。属于C++模板编程中的高级技巧,但属于模板元编程中的基本技巧。

当然我其实也并不是C++元编程方面的专家,只是搜集过一些常见的实现方式,然后做过一些测试。在这个过程中,我发现有些常见的 SFINAE 写法是有问题的,下面探讨一下。

举个例子,我们来check一下C++标准库的类中有没有 push_back 成员函数。在C++11之前,可以这样写,经过测试是没有问题的:

# include<iostream>

# include<map>

# include<list>

# include<set>

# include<string>

# include<vector>

structhas_push_back{

template< typenameC, void(C::*)( consttypenameC::value_type&)>

structHelper;

template< typenameC, void(C::*)( typenameC::value_type)>

structHelper2;

template< typenameC>

staticbooltest(...){

returnfalse;

}

template< typenameC>

staticbooltest(Helper<C, &C::push_back>*){

returntrue;

}

template< typenameC>

staticbooltest(Helper2<C, &C::push_back>*){

returntrue;

}

};

intmain{

std:: cout<< has_push_back::test< std:: list< int> >( NULL) << std:: endl;

std:: cout<< has_push_back::test< std:: map< int, int> >( NULL) << std:: endl;

std:: cout<< has_push_back::test< std:: set< int> >( NULL) << std:: endl;

std:: cout<< has_push_back::test< std:: string>( NULL) << std:: endl;

std:: cout<< has_push_back::test< std:: vector< int> >( NULL) << std:: endl;

return0;

}

SFINAE实现方式有很多种,细节处可能不同。

两个Helper类的模板参数中。第二个参数为 push_back的函数指针类型。之所以弄了两个Helper,是因为std::string的push_back的参数为char。也就是value_type类型。

而其他STL容器。则是 const value_type& 。所以才用了两个Helper。如果是检测其他成员函数,比如size则不需要这么麻烦只要一个Helper即可。

而test函数,对于返回true的模板函数,其参数是一个指针类型。所以实际check的时候,传入一个NULL就可以匹配到。

C++11之后:

# include<iostream>

# include<list>

# include<map>

# include<set>

# include<string>

# include<vector>

template< typename>

usingvoid_t= void;

template< typenameT, typenameV = void>

struct has_push_back: std::false_type {};

template< typenameT>

structhas_push_back<T, void_t<decltype(std::declval<T>.push_back(std::declval<typename T::value_type>))>>: std::true_type {};

intmain{

std:: cout<< has_push_back< std:: list< int>>::value << std:: endl;

std:: cout<< has_push_back< std:: map< int, int>>::value << std:: endl;

std:: cout<< has_push_back< std:: set< int>>::value << std:: endl;

std:: cout<< has_push_back< std:: string>::value << std:: endl;

std:: cout<< has_push_back< std:: vector< int>>::value << std:: endl;

return0;

}

C++11简洁了许多。如果需求是要检测任意成员函数,而不限定是哪个函数的话,毫无疑问,需要借助宏了。将上面的代码改变成宏的版本,push_back作为宏的一个参数,即可。

我这里为什么用 push_back 举例呢? 因为网上能找到的各种SFINAE的实现版本中,很多对于push_back的检测都是有问题的。而以上列举这两种,都能准确检测出string、vector、list中的 push_back 。

当然C++11之前的版本,需要你能枚举出push_back的各种参数种类才行,若待检测的成员函数重载版本比较多的时候,则可能很麻烦。所以还是C++11之后的版本简洁且通用

下面列举一个 常见但某些情况下会存在问题的 SFINAE 范本:

classBase{

};

classDrive: Base {

public:

voidhello{}

};

template< typenameT>

structhas_hello{

typedefcharYes[ 1]; // 或 typedef int8_t Yes;

typedefcharNo[ 2]; // 或 typedef int16_t No;

template< typename>

staticNo& has(...);

template< typenameC>

staticYes& has( decltype(&C::hello)) ;

staticconstboolvalue = sizeof(has<T>( NULL)) == sizeof(Yes);

};

intmain{

std:: cout<< has_hello<Base>::value << std:: endl;

std:: cout<< has_hello<Drive>::value << std:: endl;

}

OK,这个用来检测类中是否有hello成员函数是可以的。但是改变成push_back的版本则有问题。

# include<iostream>

# include<list>

# include<map>

# include<set>

# include<string>

# include<vector>

// 错误的示范

template< typenameT>

structhas_push_back{

typedefcharYes[ 1];

typedefcharNo[ 2];

template< typename>

staticNo& has(...);

template< typenameC>

staticYes& has( decltype(&C::push_back)) ;

staticconstboolvalue = sizeof(has<T>( NULL)) == sizeof(Yes);

};

intmain{

std:: cout<< has_push_back< std:: list< int> >::value << std:: endl;

std:: cout<< has_push_back< std:: map< int, int> >::value << std:: endl;

std:: cout<< has_push_back< std:: set< int> >::value << std:: endl;

std:: cout<< has_push_back< std:: string>::value << std:: endl;

std:: cout<< has_push_back< std:: vector< int> >::value << std:: endl;

return0;

}

上面这个是一个典型的有问题的案例——常见范本改变的push_back检测,对上面这几个类,只有string能判断为true。vector、list都check有误。

该版本也有很多其他变种。所谓变种主要是在has的返回值、value的判断方面做改编。也有一定问题,具体大家自己测试吧。

- EOF -

点击标题可跳转

1、 原来这才是 Socket!

2、 模版定义一定要写在头文件中吗?

3、 C++ Trick:什么时候需要前置声明?

关注『CPP开发者』

看精选 C++技术文章 . 加C++开发者专属圈子

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值