天秤座的唐风

总会有一个人需要你的分享~!- 唐风 -

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  13 随笔 :: 0 文章 :: 69 评论 :: 0 Trackbacks

SFINEA in C++

作者:唐风

原载于:www.cnblogs.com/liyiwen

   

    SFINAE(substitution failure is not a error) 主要用于模板函数,它是指,编译器在使用具体类型来替换模板类型参数,对模板进行实例化(展开模板)时,如果发生替换失败,那么并不会直接引发编译错误(Error),而只是简单地把这个模板从重载候选者中去除掉。

    还是看看代码吧(一个在SFINAE中常遇到的例子):

    代码段1:

    template <typename T>
    bool is_class(int T::*) {
        return true;
    }

    template <typename T>
    bool is_class(...) {
        return false;
    }

    struct Test {
    };

    int main(void) {
        std::cout<<is_class<Test>(0)<<endl;
        std::cout<<is_class<int>(0)<<endl;
    }

    运行的结果是输出:

    1

    0

    这表明,如果传给 is_class 的模板参数是一个类,那么返回 true 的那个版本就会被选中,否则false的那个版本会被选中。就是因为SFINAE在起作用。

为什么要提SFINAE?

    仅仅从程序员的角度来看,程序段1中,对相应函数选择的结果是非常符合直观的预期,与普通函数重载是很相似的感觉。

    例如,对于下面这两个函数:

    int max(int a, int b) {return a>b?a:b}
    float max(float a, float b) {return a>b?a:b}

    int main(void) {
        float x1=3.4f, x2=3.6f;
        cout<<max(x1, x2);
    }

      对于 float 型的参数,float 版本的重载自然会很被选中。在外观上看,程序段1是一样的。那么为什么程序段1就需要特别的 SFNIAE 呢?

    我想,对于普通函数的重载而言,由于这些函数的所有信息都已经完备,在发生调用之前,编译器已经可以完成对这些函数的编译,这些函数也不可能再被增加任何新的信息,可以直接产生执行代码。在函数的调用点上,编译器只需要根据参数信息选择一个合适函数的地址就可以了。

    但是,对于模板函数重载,情况就不一样了。我们分析下程序段1中,is_class<int>(0) 这个调用,在第一步的选择中,无论从模板参数的个数、函数参数的个数来看,两个 is_class 的实现都可能匹配,由于 int T::* (类成员指针)的匹配优先级比 … 的要高,所以编译器会先试图使用第一个版本进行展开。但编译展开的结果时发现 int::* 是不合法的,于是编译器就放弃展开这个函数,而取另一个函数进行展开,并得到正确的调用。

   所以,在真正发生调用(应该说真正需要被展开)之前,模板函数中的信息是不完备的,编译器无法为这些模板函数生成真正的执行代码,而只是进行一些很基本、简单的检查。所有的模板都不是“真正的代码”,它们是编译器用来生成代码的工具。在需要展开的时候,编译器从合适的候选者中选出优先级最高的一个来进行实例化(展开)。在展开后的代码如果不能正确被编译(像上面例子中 int::* 这种情况),编译器只是简单地放弃这次展开,转而寻找其它的模板。试想,如果编译器在展开失败后,直接产生一个编译错误的话,其它的函数就没有机会了,这是非常不合理的,因为:1.本次展开失败并不意味着被展开的模板代码就有问题,因为用其它类型的话还是有可能展开成功的。2.本次展开失败并不代表用于展开的类型无法找到合适的模板,其它模板可能合用。

    所以,我觉得,SFINEA 的意义就是:

    编译器在每个调用点上,只为当前需要实例化的类型寻找一个合适的模板进行展开,而不会为某一次实例化而展开所有可能合适的重载模板(函数)。

    这是编译器“智能”选择模板的表现。普通函数重载则不一样,无论是否被调用,或是无论调用点需要的是什么类型的重载,编译器会将所有参与了重载的函数一个不落的全部编译。如果对模板也采用同样的方式,那么模板将受到巨大的局限而失去意义。

    有了 SFINEA ,当我们在写模板代码的时候,就不需要担心这些模板在使用某些类型进行展开的时候会失败,从而造成程序编译错误,因为我们知道编译器只会在能展开的情况展开它们,展开失败的情况下,这些代码并不会真正进入你的程序中。

    好了,在结束本文之前,我们再看看 SFINEA “知名”的一个例子:

    程序段2:

    template <typename T>
    class is_class {
        typedef char one;
        typedef struct {char a[2];} two;

        template <typename C>
        static one test(int C::*);

        template <typename C>
        static two test(...);
    public:
        enum {value = sizeof(test<T>(0)) == sizeof(one)};
    };

    这是模板圣经《C++ templates》中的一个例子(原程序可能不完全一样),与程序段 1 不同的是,is_class<T>::value 是一个编译期的 bool 值,而程序段 1 ,ture 或是 false 是在运行期才得到的结果。is_class<T>::value 这样的“装置”(device)经常出现在模板编译中,用于根据类型的某种特性(比如,是不是一个类?)来选择不同的模板。boost 中的提供了很多类似的 device,再配合 boost::enable_if 来完成威力巨大的模板编程。

    可以说,SFINEA 几乎是随处可见的,不可或缺的重要“原则”。:)

    本文完。

 

 

 

posted on 2009-11-14 14:01 唐风 阅读(549) 评论(5)  编辑 收藏 引用 所属分类: 语言技术

评论

# re: SFINEA in C++ 2009-11-14 15:11 OwnWaterloo
代码字体挺好看的。
知名应用抄错了:

enum {value = sizeof(test<T>()) == sizeof(one)};

这里要以一个0去调用test<T>:
enum {value = sizeof(test<T>(0)) == sizeof(one)};
如果T不是内建类型,0就可以隐式转换到T的成员的指针,否则匹配省略号版本。


详细见这里,包含一个更简单的不使用SFINAE实现(代码也更多)is_buildin的方法:
http://www.cppblog.com/Charlib/archive/2009/03/16/76799.html

  回复  更多评论
  

# re: SFINEA in C++ 2009-11-14 18:00 唐风
@OwnWaterloo
谢谢指正!已经修改了~
凭记忆写的,没验证就放上去了,不严谨啊不严谨啊,呵呵

你的大作刚刚阅读了,你学得比我透~
我没用C++做过什么实际的东西,一直浮在表面上。

PS:
关于代码字体:
直接用 Windows Live writer 加上插件 from visual studion 写的,然后直接发布,感觉还不错。在 cnblog 上正文的字体没变化,不过 cppblog 上,有些字的大小变了,唉……  回复  更多评论
  

# re: SFINEA in C++ 2009-11-15 02:33 OwnWaterloo
现在和cnblogs的格式很相似了。
Windows Live Wirter可以导入pdf,然后发布到blog么?

或者Windows Live Writer在本地使用的什么格式? 可以diff(主要目的)么……

我有个想法是用某种文本文件格式的代码,比如html,latex,rst等,生成可以导入到Windows Live Writer的格式,再发布。
文本格式的代码可以diff……

  回复  更多评论
  

# re: SFINEA in C++ 2009-11-15 10:31 唐风
@OwnWaterloo
WLW 支持类似“Rich text”的编辑器(标签页是“编辑”,只要cppblog上的CSS没有另外设置,那么看到基本一致的效果)与一个纯文件的编辑器(标签页是“源代码”,可以获取相应的html代码),两个是连动的。

有时候我大面积更改已发布的文章中内容的时候,也是先在编辑页面修改,然后在源代码页面把html代码拷出来,直接帖在cppblog(cnblogs)的编辑器里(纯文本模式)。

WLW 在本地还有什么其它格式我就不清楚了,不过我想应该想满足diff的要求。
不过直接导入PDF貌似不行……传说word可以直接帖,保留格式,不过我没用过,我很久没用word了……哈哈

  回复  更多评论
  

# re: SFINEA in C++ 2009-11-16 03:58 OwnWaterloo
@唐风
我试试 …… 谢谢~_~

  回复  更多评论
  


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理