【<专题讨论>[模板模式]<3>】SFINAE

xueweizhong 2003-11-20 10:20:16
加精
This "substitution-failure-is-not-an-error" (SFINAE) principle is clearly an important ingredient to make the overloading of function templates practical.

这是C++ template : the complete guide里的一段话,SFINAE几乎在模板库
中无处不在。BOOST, LOKI中都用到了这个东东。

呵呵,希望大家在这里谈谈这个东东,看看它的威力
究竟有多大,呵呵。
...全文
246 40 打赏 收藏 举报
写回复
40 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
ajoo 2003-12-31
帮你UP一下吧。同时也再解释以一下我的观点。
我之所以觉得SFINAE更好,是因为,如果语言不支持它,我要在CONSTRAINT里说明X<T>必须是一个合法类型几乎是不可能的。
因为你一写X<T>,如果它合法,自然没关系,如果不合法,就马上编译出错,没有任何机会说:好吧,不合法,就不符合要求。
这就象一个没有机会catch的exception,能有什么用呢?

反过来,SFIAE却很容易做到。比如说,我需要让X<T>不是合法类型的时候编译出错,可以很容易地如此实现:
template<class T>
void f(T t, X<T>* p = 0){
static_assert(false);
}

所以,SFINAE明显更灵活。
  • 打赏
  • 举报
回复
xueweizhong 2003-12-23
你想说一件什么事情?
  • 打赏
  • 举报
回复
genvy 2003-12-23
又在试了
template<class T> void check( sample<sizeof(new T)>* ) { }
template<class T> void check(...) { }
发现http://www.comeaucomputing.com/tryitout/永远都是用check(...)
大概是因为sample<sizeof(new T)>* 用了new T是不正确的,因为它不是一个constant值
改成
template<class T> void check( sample<1>*,int =sizeof(new T)) { }
template<class T> void check(...) { }
又发现永远都是用check( sample<1>*,int =sizeof(new T))
然后我把main中的check<X>(0);改成check<X>(1);终于得到了正确的结果
大概是因为0可以表示任意的指针,而1就不能
  • 打赏
  • 举报
回复
genvy 2003-12-23
我说的最优匹配大概大概类似more specialized than的意思,T*应该说比...更特殊把。嗯,刚开始我可能对substitution的意思有点误解。我想的最优匹配和SFINAE有点类似。现在看来substitution和declare有点类似,而实例化和define有点类似
下面的又可以了
template <typename T,typename =T::TS> class VValidor;
^^^^^加上这个,让他在substitution时失败
上面是声明
template <typename T,typename> class VValidor
{
public:
typedef int vint;
typedef typename T::TS TS;
};
class Testor
{
public:
typedef int TS;
};
template <typename T> char is_ptr_of_class_defined_TS(T*,typename VValidor<T>::vint =0)
{
//typename T::TS tt;
//UNUSED_VAR(tt);
return 1;
}
int is_ptr_of_class_defined_TS(...)
{
return 0;
}
int main()
{
int i=0;
is_ptr_of_class_defined_TS(&i);
Testor t;
is_ptr_of_class_defined_TS(&t);
return 0;
}
  • 打赏
  • 举报
回复
xueweizhong 2003-12-23
>to genvy(Leo)
你例子中的:
int i=0;
is_ptr_of_class_defined_TS(&i); //int没有定义TS,
// 将会出现编译错误,
// 这刚好是"实例化失败是错误"。
// 这里不发生SFINAE

>to genvy(Paul):

当你改写为:
template <typename T>
char is_ptr_of_class_defined_TS(T*,typename T::TS* =0)
^^^^^^^^^^^^^^^^^^^
-----#1
int is_ptr_of_class_defined_TS(...)
-----#2

这个样子后

int i=0;
is_ptr_of_class_defined_TS(&i);
这一句会导致SFINAE
由于SFINAE不是错误,导致的结果是#1不会被添加为重载后选函数,
所以#2被选中。

》>to genvy(Paul) && to genvy(Leo) :
1 你的例子没有什么奇怪的地方。

2 另外
你说的“模板最优匹配”是什么概念?
标准里好象没这种说法。
是否指: "more specialized than"?
比如
template <typename T>
void foo(T); -----------#1
template <typename T>
void foo(T*); ----------#2
这里#2比#1更特殊一些。

但在你的例子没有这个东东的丝毫痕迹,所以
肯定是我还没有理解到。

3:既然是我没有理解,
所以只能期盼你说得更详细一些,
盼指教...........
  • 打赏
  • 举报
回复
genvy 2003-12-23
不过这个又不行了
template <typename T> class VValidor
{
public:
typedef int vint;
typedef typename T::TS TS;
};
class Testor
{
public:
typedef int TS;
};
template <typename T> char is_ptr_of_class_defined_TS(T*,typename VValidor<T>::TS =0)
{
//typename T::TS tt;
//UNUSED_VAR(tt);
return 1;
}
int is_ptr_of_class_defined_TS(...)
{
return 0;
}
int main()
{
int i=0;
is_ptr_of_class_defined_TS(&i);
Testor t;
is_ptr_of_class_defined_TS(&t);
return 0;
}
  • 打赏
  • 举报
回复
genvy 2003-12-23
不过令人欣慰的是下面这个可以,不过这个似乎也可以用模板匹配来解释
template <typename T> char is_ptr_of_class_defined_TS(T*,typename T::TS* =0)
{
return 1;
}
int is_ptr_of_class_defined_TS(...)
{
return 0;
}
int main()
{
int i=0;
is_ptr_of_class_defined_TS(&i);
Testor t;
is_ptr_of_class_defined_TS(&t);
return 0;
}
  • 打赏
  • 举报
回复
genvy 2003-12-23
我觉得xueweizhong举的所有例子似乎都可以用模板最优匹配来说明,我甚至怀疑SFINAE规则是否真正存在,如果下面的例子能在某编译器上通过或许能打消我的疑虑
我在http://www.comeaucomputing.com/tryitout/上用comeau C++编译失败了
template <class X> void UNUSED_VAR(X&)
{
}

class Testor
{
public:
typedef int TS;
};
template <typename T> char is_ptr_of_class_defined_TS(T*)
{
typename T::TS tt;
UNUSED_VAR(tt);
return 1;
}

int is_ptr_of_class_defined_TS(...)
{
return 0;
}

int main()
{
int i=0;
is_ptr_of_class_defined_TS(&i); //int没有定义TS,
//应调用is_ptr_of_class_defined_TS(...)
Testor t;
is_ptr_of_class_defined_TS(&t);//定义TS,
//应调用is_ptr_of_class_defined_TS(T*)
return 0;
}
  • 打赏
  • 举报
回复
ajoo 2003-12-23
我觉得还是不是错误更符合逻辑。

而且,要报错,不知道为什么不能用is_const这种类似的assert?

而如果语言不支持SFINAE, 自己要模拟一个可就费劲了。

  • 打赏
  • 举报
回复
xueweizhong 2003-12-23
从rani的例子说起,如果要引起一些思考的话,
那就是这个例子暗示着
"Daveed的判断是正确的。"

但已经没什么理由了,等着什么时候看到了
ISO/IEC 14882 : 2003
再翻翻相关章节吧,
可能总是会得到失望。
  • 打赏
  • 举报
回复
xueweizhong 2003-12-22
不好意思,漏东西了:

template <typename T>
struct error_on_const
{
typedef int type;

typedef error_on_const<T>::const_not_allowed_type
const_not_allowed_type;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//此处是为了导致 “实例化失败就是错误”

};

此处是部分特殊化,
漏掉了template-id,
呵呵,应该是:
template <typename T>
struct error_on_const<T const>
//^^^^^^^^^
{
....
};

  • 打赏
  • 举报
回复
xueweizhong 2003-12-22
最近在COMP.STD.C++上Rani在讨论
auto_ptr的改进,

“SFINAE”(替换失败不是错误)
相对的是,他使用了
“实例化失败就是错误”


template <typename T>
struct error_on_const
{
typedef int type;
};

template <typename T>
struct error_on_const
{
typedef int type;

typedef error_on_const<T>::const_not_allowed_type
const_not_allowed_type;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//此处是为了导致 “实例化失败就是错误”

};

template <typename T>
void accept_non_const(T&, error_on_const<T>::type = 0)
{
}

template <typename T>
void accept_non_const(T const&)
{
}

int main()
{
const float foo;
accept_non_const(foo); // error : 因为foo是const 类型
}
  • 打赏
  • 举报
回复
lyr311 2003-12-22
MK!
  • 打赏
  • 举报
回复
flyccloud 2003-12-22
关注一下
  • 打赏
  • 举报
回复
jhwh 2003-12-22
babysloth(小懒虫虫) ,不懂就别开口。
  • 打赏
  • 举报
回复
ajoo 2003-12-20
我是向来对c++里面的所谓现代新技术唱反调的。但是这个SFINAE我觉得很好啊。
本来就应该这样嘛。

假如说,c++引入concept概念,你说我这个T呀,必须符合X<T> = Y<T>。此处,隐含着三个条件:X<T>是个合法类型;Y<T>是个合法类型;X<T>=Y<T>。
所以说,constraint里面引用的每个类型表达式,都有一个前提,就是这个类型表达式必须是个合法类型。如果不是,就证明不符合这个constraint。多合乎逻辑呀。

就现在没有constraint的特化来说:
template<class T>void f(T* t){...}
也一样等于是说如果这个参数类型是一个T*的话,就符合我的条件,否则就不符合。一样也等于是一个SFINAE嘛。


关键我觉得这个东西非常的和逻辑,而且和现在的C++魔板模型很一致。不会引入什么特殊处理和补丁。


当然,XUEWEIZHONG的一些对这个东西的应用的例子,我感觉仍然是不如一个完整的concept方案清楚,简单。比如说:
template<class InputIterator, class Predicate>
InputIterator find(InputIterator first, InputIterator last,
Predicate pred,
preicatable<Predicate>::type*=0);
可行。但是,仍然有hack的迹象。最理想的解决办法仍然是:
template<InputIterator I, Predicate P>
InputIterator find(I first, I last,
P pred);


对了,xueweizhong,你给的例子,如果按照SFINAE的原则,我怎么觉得应该是:
> template<class T> void check( sample<sizeof(new T)>* ) { }
> template<class T> void check( int ) { }
>
> int main() {
> check<X>(0);
> return 0;
> }
1。优先解析到check(int),根本不看check(sample)。没SFINAE什么事。

> template<class T> void check( sample<sizeof(new T)>* ) { }
> template<class T> void check(...) { }

1。优先解析到check(sample)。
2。sizeof(new T)失败,SFINAE生效,这个版本退出竞争,而不是报错。
3。继续解析到check(...)。成功。


你说的“ 因为 sizeof(new T)虽然是无效表达式,
但不是无效类型,所以这里不发生SFINAE。”
是什么意思?既然new T是无效表达式,sizeof(new T)自然也是无效表达式。那么sample<sizeof(new T)>自然是无效类型了。

  • 打赏
  • 举报
回复
xueweizhong 2003-12-20
to ajoo:

>你说的“ 因为 sizeof(new T)虽然是无效表达式,
> 但不是无效类型,所以这里不发生SFINAE。”
>是什么意思?既然new T是无效表达式,sizeof(new T)自然也是无效表达式。那么>sample<sizeof(new T)>自然是无效类型了。

呵呵,“sizeof(new T)不发生SFINAE”这是DAVID在comp.lang.c++做出的判断。
我也倾向于你的观点。

  • 打赏
  • 举报
回复
comeonstuding 2003-12-19
up
  • 打赏
  • 举报
回复
xueweizhong 2003-12-09
最近又想起CRTP了,和SFINAE结合一下,可以得到以下一种方式:

template <typename T>
void find(T); // -------------#1 最一般方式

template <typename Son>
struct Mine : Son {};

template <typename Son>
void find(Mine<Son> const& mine)
{
static_cast<Son const&>(mine).find();
}
//--------------#2 我自己的,
// 为了和#1不产生重载冲突,采用
// CRTP方式

对于#2的一种简单的用法便是:

struct Foo : Mine<Foo>
{
void find() {....}
};


这种使用方式大概就是"泛型多态"的一种吧,呵呵。
  • 打赏
  • 举报
回复
xueweizhong 2003-11-30
现在我们发现的是在下面两种情况下
SFINAE不能被安全使用:

1:在函数返回值类型中:

  template <typename T>
iterator_traits<T>::value_type
get(T);

int get(int);

int n = get(1);
  这个例子在许多编译器上编译出错,而
  没有采用SFINAE.

2: 模板类型参数出现在sizeof中。
  比如前面帖子中出现过的
  template <typename T>
void check(sample<sizeof(new T)>*);

1 的问题更象是编译器的BUG,而
2 的问题则更象是c++std的问题,没有说清楚而
  导致编译器实现各不相同。
 而我的倾向是:
  这里的无效值sizeof(new T)
 应该能导致sample<sizeof(new T)>是个无效类型,
  导致SFINAE失败,导致其永远不能被调用。


除了1和2的情况外,SFINAE的使用应该是安全的。
希望大家再列举一些使用相对于不同编译器,不能安全使用
SFINAE的情况...期待一下☆☆☆☆☆☆☆★★★★★★

而一些简单使用SFINAE的例子是没有问题,而且很安全的:


举个例子来说:

template <typename T>
void copy(T const& source, T& target)
{
...// 一般实现
}

template <typename T>
void copy(T const& source, T& target, POD_has_type<T>::type*=0)
{
memmove(target, source, sizeof(T));
}

这里POD_has_type<T>当
T为POD是 POD_has_type<T>::type有定义
否则   POD_has_type<T>::type没有定义 

  • 打赏
  • 举报
回复
加载更多回复(20)
发帖
C++ 语言

6.2w+

社区成员

C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
帖子事件
创建了帖子
2003-11-20 10:20
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下