C++ 基于类成员存在/不存在的SFINAE

C++ 基于类成员存在/不存在的SFINAE,c++,c++11,sfinae,C++,C++11,Sfinae,我对SFINAE有基本的了解,例如如何在工作时启用。我最近遇到过,我花了一个多小时试图理解它是如何实际工作的,但没有用 此代码的目标是根据类中是否有特定成员来重载函数。这是复制的代码,它使用C++11: template <typename T> struct Model { vector<T> vertices; void transform( Matrix m ) { for(auto &&vertex : v

我对SFINAE有基本的了解,例如
如何在
工作时启用。我最近遇到过,我花了一个多小时试图理解它是如何实际工作的,但没有用

此代码的目标是根据类中是否有特定成员来重载函数。这是复制的代码,它使用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
    }
};
模板结构模型
{
向量顶点;
空洞变换(矩阵m)
{
用于(自动&顶点:顶点)
{
vertex.pos=m*vertex.pos;
modifyNormal(顶点,m,特殊_());
}
}
私人:
结构通用{};
特殊结构:一般结构{};
模板结构int{typedef int type;};
模板
无效修改正常(左侧和左侧、右侧和右侧、特殊){
lhs.normal=rhs*lhs.normal;
}
模板
无效修改法线(左侧和左侧、右侧和右侧、常规){
//无所事事
}
};

就我的一生而言,我无法理解这种机制是如何工作的。具体来说,
typename int\\::type=0
对我们有什么帮助,为什么我们在这个方法中需要一个额外的类型(
special
/
general
)。

special
/
general
是用来让编译器区分这两个方法的类型。请注意,没有使用第三个参数。一般实现不做任何事情,但在特殊情况下,它会修改正常的。 还请注意,
特殊
是从
常规
派生而来的。这意味着,如果未定义
modifyNormal
的专用版本(SFINAE),则一般情况适用;如果存在专用版本,则将选择它(更具体)

现在在
modifyNormal
的定义中有一个开关;如果类型(模板的第一个参数)没有名为
normal
的成员,则模板将失败(SFINAE,不要抱怨它,这是SFINAE的诀窍),并且将应用
modifyNormal
的其他定义(一般情况)。如果该类型定义了名为
normal
的成员,则模板的第三个参数可以解析为附加的第三个参数模板(默认值为0的
int
)。第三个参数对函数没有任何作用,仅用于SFINAE(模式应用于它)

为什么在这个方法中需要一个额外的类型(
特殊的
/
常规的

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

typename int\uuu::type=0
对我们有什么帮助

这是一个具有默认值的模板参数。默认值存在,因此
transform
函数不必指定它,这一点很重要,因为另一个
modifyNormal
函数没有此模板参数。此外,此模板参数仅用于调用SFINAE

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

<> >如果发生故障,<代码>修改为正常/<代码>函数,将“<代码>特殊代码< /代码>类型从重载集合中移除以考虑。这只剩下
modifyNormal
函数采用
general
类型,因为
special
是-a
general
类型,所以一切仍然有效

如果未发生替换故障,则将使用使用
特殊类型的
modifyNormal
功能,因为这是更好的匹配


注意:常规类型是一个
struct
,因此默认情况下继承是
public
,允许is-a关系而不使用
public
关键字


编辑:


你能解释一下为什么我们首先要使用复杂的
typename int_216;::type
机制吗

如上所述,这用于触发SFINAE行为。然而,当你把它分解的时候,它不是很复杂。它的核心是为某个类型的
T
实例化
int\ucode>结构的实例,并定义了
type
数据类型:

int_<T>::type

最后,用于实例化
int\uu
结构的实际类型是什么?这是由
decltype(Lhs::normal)
确定的,它报告了
Lhs::normal
的类型。如果type
Lhs
type有一个
normal
数据成员,那么一切都会成功。但是,如果没有,则会出现替换失败,其重要性已在上文中解释。

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

modifyNormal(vertex, m, special_());
无论如何,因为
general
继承自
special
,所以两者都是有效的,如果第一个在类型替换期间失败,将选择第二个

所以,这就像是说——让我们试一下,好像这个方法确实存在,但是保持冷静,因为我们有一个什么都不做的全面回调

为什么会失败?
这就是
int\uu
参与游戏的地方。
如果
decltype
(让我说)由于
Lhs
中缺少成员方法
normal
而给出错误,
int\u
无法专门化,替换实际上失败了,但由于SFINAE,此失败不是一个错误
modifyNormal(vertex, m, special_());