我对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_
)。
答案 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)
类型general
和special
用于强制编译器在尝试解析调用时首次尝试选择第一个函数(即更好的匹配)。
请注意,呼叫是:
modifyNormal(vertex, m, special_());
无论如何,因为general
继承自special
,所以两者都有效,如果第一个在类型替换期间失败,则会选择第二个。
所以,它喜欢说 - 让我们好像该方法确实存在,但要保持冷静,因为我们有一个无所事事的回调
为什么会失败?
那是int_
参与游戏的地方
如果decltype
(我说)发出错误,因为normal
中错过了成员方法Lhs
,则int_
无法专门化替换实际上失败了,但是由于SFINAE,只要存在可以尝试的另一个替换(并且在我们的情况下,存在general
作为参数的方法,这种失败不是错误,仍然是与原始电话不太精确匹配。)