【问题标题】:SFINAE Based on Class Member Existence/Absence基于类成员存在/不存在的 SFINAE
【发布时间】:2016-03-24 06:33:53
【问题描述】:

我对 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_&lt;decltype(Lhs::normal)&gt;::type = 0 对我们有什么帮助,为什么我们在这个方法中需要一个额外的类型(special_/general_)。

【问题讨论】:

  • 在我看来,该示例无效;如果Lhs 是左值,则转发引用不会触发decltype(Lhs::normal)。应该是decltype(std::declval&lt;Lhs&gt;().normal)
  • @PiotrSkotnicki 示例运行,我测试了它。
  • 它编译,但你检查它是否给出正确的结果?
  • @Phonon 好吧,比较thisthis,而不是和我争论
  • @Phantom 因为转发引用成为左值的左值引用,因此,例如A&amp;::normal 永远无效(请参阅与号)

标签: c++ c++11 sfinae


【解决方案1】:

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

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

typename int_&lt;decltype(Lhs::normal)&gt;::type = 0 帮助我们做什么

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

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

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

因此,如果发生故障,则采用 special_ 类型的 modifyNormal 函数将从要考虑的重载集中删除。这只留下 modifyNormal 函数采用 general_ 类型,并且由于 special_ IS-A general_ 类型一切仍然有效。

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


注意:general_类型是struct所以默认继承是public,允许IS-A关系不使用public关键字。


编辑:

您能否评论一下我们为什么首先使用精心设计的typename int_&lt;decltype(Lhs::normal)&gt;::type 机制?

如上所述,这用于触发 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 数据成员,那么一切都会成功。但是,如果不是,则表示替换失败,其重要性已在上文解释。

【讨论】:

  • 您能评论一下我们为什么首先使用精心设计的typename int_&lt;decltype(Lhs::normal)&gt;::type 机制吗?难道没有更简单的方法让它失败吗?
  • @Phonon 我添加了更多信息以尝试为您澄清细节。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    modifyNormal(vertex, m, special_());
    

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

    所以,就像说 - 让我们假设该方法确实存在,但保持冷静,因为我们有一个什么都不做的包罗万象的回调

    为什么会失败?
    这就是int_ 参与游戏的地方。
    如果decltype(让我说)给出错误,因为Lhs 中缺少成员方法normal,则int_ 不能被专门化并且替换实际上失败了,但感谢SFINAE ,只要存在可以尝试的另一个替换(并且在我们的例子中存在,以general 作为参数的方法,与原始调用的匹配仍然不太精确),此失败就不是错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-23
      • 2016-06-26
      • 1970-01-01
      • 2016-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多