SFINAE 基于 Class 成员 Existence/Absence
SFINAE Based on Class Member Existence/Absence
我对SFINAE有基本的了解,例如enable_if
是如何工作的。我最近遇到了 this answer,我花了一个多小时试图了解它的实际工作原理,但无济于事。
此代码的目标是根据 class 中是否包含特定成员来重载函数。这是复制的代码,它使用 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_
)。
special_
/general_
是用来让编译器区分两种方法的类型 modifyNormal
。请注意,不使用第三个参数。一般实现什么也不做,但在特殊情况下它会修改法线。
另请注意,special_
派生自 general_
。这意味着如果未定义 modifyNormal
的特殊版本 (SFINAE),则适用一般情况;如果存在专门版本,则将选择它(更具体)。
现在modifyNormal
的定义中有一个开关;如果类型(模板的第一个参数)没有名为 normal
的成员,则模板失败(SFINAE,不要抱怨它,这是 SFINAE 的技巧),它将是 [=12 的另一个定义=] 将适用(一般情况)。如果类型定义了一个名为 normal
的成员,那么模板的第三个参数可以解析为额外的第三个参数模板(int
默认等于 0)。第三个参数对函数没有任何意义,仅用于 SFINAE(模式适用于它)。
why do we need an extra type (special_
/general_
) in this method
这些仅用于允许 modifyNormal
函数被不同的实现重载的目的。 特别 的地方在于 special_
使用 IS-A 关系,因为它继承自 general_
。此外,transform
函数总是调用采用 special_
类型的 modifyNormal
重载,请参阅下一部分。
what does typename int_<decltype(Lhs::normal)>::type = 0
help us do
这是一个具有默认值的模板参数。默认值存在,因此 transform
函数不必指定它,这很重要,因为另一个 modifyNormal
函数没有此模板参数。此外,添加此模板参数仅用于调用 SFINAE。
http://en.cppreference.com/w/cpp/language/sfinae
When substituting the deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.
因此,如果发生故障,采用 special_
类型的 modifyNormal
函数将从要考虑的重载集中删除。这只会让 modifyNormal
函数采用 general_
类型,因为 special_
IS-A general_
类型一切仍然有效。
如果没有发生替换失败,那么将使用使用 special_
类型的 modifyNormal
函数,因为它是更好的匹配。
注意: general_
类型是一个 struct
所以默认继承是 public
,允许没有IS-A关系使用 public
关键字。
编辑:
Can you comment on why we use the elaborate typename int_<decltype(Lhs::normal)>::type
mechanism in the first place?
如上所述,这用于触发 SFINAE 行为。但是,当您将其分解时,它并不是很详尽。它的核心是要为某种类型 T
实例化 int_
结构的一个实例,并且它定义了一个 type
数据类型:
int_<T>::type
由于这是在模板中使用的,因此需要添加 typename
关键字,请参阅 When is the “typename” keyword necessary?。
typename int_<T>::type
最后,用于实例化 int_
结构的实际类型是什么?这由 decltype(Lhs::normal)
确定,它报告 Lhs::normal
的类型。如果类型 Lhs
类型有一个 normal
数据成员,那么一切都会成功。但是,如果不是,则表示替换失败,这一点的重要性已在上面进行了解释。
类型 general
和 special
用于强制编译器在尝试解析调用时选择第一个函数(匹配更好)作为第一次尝试。
注意调用是:
modifyNormal(vertex, m, special_());
无论如何,因为general
继承自special
,两者都有效,如果第一个在类型替换期间失败,将选择第二个。
所以,这就像说 - 让我们试一试,就好像该方法确实存在一样,但请保持冷静,因为我们有一个不执行任何操作的万能回调。
为什么会失败?
这就是 int_
参与游戏的地方。
如果decltype
(让我说)报错因为成员方法normal
在Lhs
中丢失,int_
不能专门化并且替换实际上失败了,但是由于 SFINAE,只要存在另一个可以尝试的替换(并且在我们的例子中存在,该方法以 general
作为参数,那么这个失败就不是错误仍然是原始呼叫的不太精确的匹配。
我对SFINAE有基本的了解,例如enable_if
是如何工作的。我最近遇到了 this answer,我花了一个多小时试图了解它的实际工作原理,但无济于事。
此代码的目标是根据 class 中是否包含特定成员来重载函数。这是复制的代码,它使用 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_
)。
special_
/general_
是用来让编译器区分两种方法的类型 modifyNormal
。请注意,不使用第三个参数。一般实现什么也不做,但在特殊情况下它会修改法线。
另请注意,special_
派生自 general_
。这意味着如果未定义 modifyNormal
的特殊版本 (SFINAE),则适用一般情况;如果存在专门版本,则将选择它(更具体)。
现在modifyNormal
的定义中有一个开关;如果类型(模板的第一个参数)没有名为 normal
的成员,则模板失败(SFINAE,不要抱怨它,这是 SFINAE 的技巧),它将是 [=12 的另一个定义=] 将适用(一般情况)。如果类型定义了一个名为 normal
的成员,那么模板的第三个参数可以解析为额外的第三个参数模板(int
默认等于 0)。第三个参数对函数没有任何意义,仅用于 SFINAE(模式适用于它)。
why do we need an extra type (
special_
/general_
) in this method
这些仅用于允许 modifyNormal
函数被不同的实现重载的目的。 特别 的地方在于 special_
使用 IS-A 关系,因为它继承自 general_
。此外,transform
函数总是调用采用 special_
类型的 modifyNormal
重载,请参阅下一部分。
what does
typename int_<decltype(Lhs::normal)>::type = 0
help us do
这是一个具有默认值的模板参数。默认值存在,因此 transform
函数不必指定它,这很重要,因为另一个 modifyNormal
函数没有此模板参数。此外,添加此模板参数仅用于调用 SFINAE。
http://en.cppreference.com/w/cpp/language/sfinae
When substituting the deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.
因此,如果发生故障,采用 special_
类型的 modifyNormal
函数将从要考虑的重载集中删除。这只会让 modifyNormal
函数采用 general_
类型,因为 special_
IS-A general_
类型一切仍然有效。
如果没有发生替换失败,那么将使用使用 special_
类型的 modifyNormal
函数,因为它是更好的匹配。
注意: general_
类型是一个 struct
所以默认继承是 public
,允许没有IS-A关系使用 public
关键字。
编辑:
Can you comment on why we use the elaborate
typename int_<decltype(Lhs::normal)>::type
mechanism in the first place?
如上所述,这用于触发 SFINAE 行为。但是,当您将其分解时,它并不是很详尽。它的核心是要为某种类型 T
实例化 int_
结构的一个实例,并且它定义了一个 type
数据类型:
int_<T>::type
由于这是在模板中使用的,因此需要添加 typename
关键字,请参阅 When is the “typename” keyword necessary?。
typename int_<T>::type
最后,用于实例化 int_
结构的实际类型是什么?这由 decltype(Lhs::normal)
确定,它报告 Lhs::normal
的类型。如果类型 Lhs
类型有一个 normal
数据成员,那么一切都会成功。但是,如果不是,则表示替换失败,这一点的重要性已在上面进行了解释。
类型 general
和 special
用于强制编译器在尝试解析调用时选择第一个函数(匹配更好)作为第一次尝试。
注意调用是:
modifyNormal(vertex, m, special_());
无论如何,因为general
继承自special
,两者都有效,如果第一个在类型替换期间失败,将选择第二个。
所以,这就像说 - 让我们试一试,就好像该方法确实存在一样,但请保持冷静,因为我们有一个不执行任何操作的万能回调。
为什么会失败?
这就是 int_
参与游戏的地方。
如果decltype
(让我说)报错因为成员方法normal
在Lhs
中丢失,int_
不能专门化并且替换实际上失败了,但是由于 SFINAE,只要存在另一个可以尝试的替换(并且在我们的例子中存在,该方法以 general
作为参数,那么这个失败就不是错误仍然是原始呼叫的不太精确的匹配。