SFINAE基于班级成员存在/缺席

时间:2016-03-24 06:33:53

标签: c++ c++11 sfinae

我对SFINAE有基本的了解,例如enable_if如何运作。我最近遇到this answer,我花了一个多小时试图了解它实际上是如何工作无济于事。

此代码的目标是根据类中是否包含特定成员来重载函数。这是复制的代码,它使用C ++ 11:

template <typename T> struct Model
{
    vector<T> vertices;

    void transform( Matrix m )
    {
        for(auto &&vertex : vertices)
        {
          vertex.pos = m * vertex.pos;
          modifyNormal(vertex, m, special_());
        }
    }

private:

    struct general_ {};
    struct special_ : general_ {};
    template<typename> struct int_ { typedef int type; };

    template<typename Lhs, typename Rhs,
             typename int_<decltype(Lhs::normal)>::type = 0>
    void modifyNormal(Lhs &&lhs, Rhs &&rhs, special_) {
       lhs.normal = rhs * lhs.normal;
    }

    template<typename Lhs, typename Rhs>
    void modifyNormal(Lhs &&lhs, Rhs &&rhs, general_) {
       // do nothing
    }
};

对于我的生活,我无法理解这种机制的运作方式。具体来说,typename int_<decltype(Lhs::normal)>::type = 0帮助我们做了什么,为什么我们在此方法中需要额外的类型(special_ / general_)。

3 个答案:

答案 0 :(得分:3)

  

为什么我们在此方法中需要额外的类型(special_ / general_

这些仅用于允许modifyNormal函数使用不同的实现重载。关于它们的特殊special_使用IS-A关系,因为它继承自general_。此外,transform函数始终调用采用modifyNormal类型的special_重载,请参阅下一部分。

  

typename int_<decltype(Lhs::normal)>::type = 0帮助我们做什么

这是一个带有默认值的模板参数。默认值存在,因此transform函数不必指定它,这很重要,因为其他modifyNormal函数没有此模板参数。此外,仅添加此模板参数以调用SFINAE。

http://en.cppreference.com/w/cpp/language/sfinae

  

当使用推导类型替换模板参数失败时,将从重载集中丢弃特化,而不是导致编译错误。

因此,如果发生故障,则从要考虑的重载集中删除采用modifyNormal类型的special_函数。这只会使modifyNormal函数保留general_类型,而special_ IS-A general_类型的所有内容仍然有效。

如果未发生替换失败,则将使用modifyNormal类型的special_函数,因为它更匹配。

注意: general_类型为struct,因此默认情况下继承为public,允许IS-A关系而不使用{{1关键字。

修改

  

您能否首先评论我们为何使用精心设计的public机制?

如上所述,这用于触发SFINAE行为。但是,当你将其分解时,它并不是很精细。在它的核心,它希望为某种类型typename int_<decltype(Lhs::normal)>::type实例化int_结构的实例,并且它定义了T数据类型:

type

由于这是在模板中使用,因此需要添加int_<T>::type 关键字,请参阅When is the “typename” keyword necessary?

typename

最后,用于实例化typename int_<T>::type 结构的实际类型是什么?这由int_确定,报告decltype(Lhs::normal)的类型。如果类型Lhs::normal类型具有Lhs数据成员,则一切都成功。但是,如果它没有,则存在替换失败,并且上面解释了其重要性。

答案 1 :(得分:1)

special_ / general_是用于让编译器区分两种方法modifyNormal的类型。请注意,不使用第三个参数。一般实现什么都不做,但在特殊情况下它会修改法线。 另请注意,special_源自general_。这意味着如果未定义modifyNormal的专用版本(SFINAE),则适用一般情况;如果存在专用版本,则会选择它(更具体)。

现在modifyNormal的定义有一个转换;如果类型(模板的第一个参数)没有名为normal的成员,则模板失败(SFINAE并不抱怨它,这是关于SFINAE的伎俩),它将是{{1的另一个定义将适用(一般情况)。如果类型定义了名为modifyNormal的成员,那么模板的第三个参数可以解析为另一个第三个参数模板(normal默认等于0)。第三个参数对该函数没有任何意义,仅适用于SFINAE(该模式适用于它)。

答案 2 :(得分:1)

类型generalspecial用于强制编译器在尝试解析调用时首次尝试选择第一个函数(即更好的匹配)。
请注意,呼叫是:

modifyNormal(vertex, m, special_());

无论如何,因为general继承自special,所以两者都有效,如果第一个在类型替换期间失败,则会选择第二个。

所以,它喜欢说 - 让我们好像该方法确实存在,但要保持冷静,因为我们有一个无所事事的回调

为什么会失败?
那是int_参与游戏的地方 如果decltype(我说)发出错误,因为normal中错过了成员方法Lhs,则int_无法专门化替换实际上失败了,但是由于SFINAE,只要存在可以尝试的另一个替换(并且在我们的情况下,存在general作为参数的方法,这种失败不是错误,仍然是与原始电话不太精确匹配。)