【问题标题】:SFINAE Member Pass throughSFINAE会员通行证
【发布时间】:2012-12-30 23:45:36
【问题描述】:

我想知道是否可以创建一个类来作为 std::enable_if 和 SFINAE 成员检测器之间的组合。

class foo
{
public:
    int bar;
};

template <class T>
typename enable_if_has_bar<T>::type ReturnBar (const T& value)
{
    return value.bar;
}

所以我尝试这样做。

class foo
{
public:
    int bar;
};

template <class C, C>
class Check;

template <class T, class Enable = void>
class enable_if_has_bar
{};

template <class T>
class enable_if_has_bar<T, Check <decltype(&T::bar),&T::bar>>
{
public:
    typedef decltype(static_cast<T*>(0)->*static_cast<decltype(&T::bar)>(0)) type;
};

template <class T>
typename enable_if_has_bar<T>::type ReturnBar (const T& value)
{
    return value.bar;
}

int main ()
{
    foo foobar;
    foobar.bar = 42;

    cout << ReturnBar(foobar) << endl;
}

(http://ideone.com/WKTfmQ)

它似乎不起作用,而且我对 SFINAE 的美术还不够精通。也许有人可以改进/修复它?因为我很茫然。

【问题讨论】:

  • 不应该是ReturnBar(foobar)&amp;T::bar吗?并不是说它即使在那时也能工作,但它会更接近工作。
  • 哦,那里的错字很严重。谢谢
  • 有趣的小玩笑:我将 SFINAE 发音为“SNIFNAE”(是的,有两个 n),所以听起来像“SNIF-NAY”。呵呵

标签: c++ templates sfinae


【解决方案1】:

我通常更喜欢按照您的尝试创建自定义 enable_if 样式类型,因为我发现使用单个特征类型而不是 enable_if&lt;some_trait&lt;T&gt;, another_trait&lt;T&gt;&gt; 的组合阅读代码更清晰。但在这种情况下,您的代码会出现一些问题,使其无法正常工作。

永远不会选择您的 enable_if_has_bar 特化,ReturnBar 的返回类型只是实例化主模板 enable_if_has_bar&lt;foo, void&gt;,并且永远不会定义嵌套的 type。没有什么会导致特化被实例化,所以没有任何东西可以检查 T::bar 是否是一个有效的表达式。

您的decltype(static_cast&lt;T*&gt;(0)-&gt;*static_cast&lt;decltype(&amp;T::bar)&gt;(0)) 表达式将导致int&amp; 而不是您可能想要的int。这是因为decltype(foobar.*(&amp;foo::bar)) 等价于decltype(foobar.bar)foobar.bar 是一个左值,所以decltypeint&amp;。如果函数 ReturnBar 返回 int&amp; 将无法编译,因为参数 value 是 const,因此您不能将 value.bar 绑定到非 const int&amp;

这是一个工作版本:

template <class T>
  class has_bar
  {
    template<typename U, typename = decltype(&U::bar)>
      static std::true_type
      test(U*);

    static std::false_type
    test(...);

  public:
    static const int value = decltype(test((T*)nullptr))::value;
  };

template<typename T, bool = has_bar<T>::value>
  struct enable_if_has_bar
  { };

template<typename T>
  struct enable_if_has_bar<T, true>
  : std::decay<decltype(std::declval<T&>().*(&T::bar))>
  { };

这里首先声明了helper has_bar来回答类型是否有嵌套成员的问题。该助手使用 SFINAE 获取 truefalse 值。如果&amp;T::bar 是一个有效的表达式,那么将使用test 的第一个重载,它返回true_type,因此value 将设置为true_type::value,即true。否则将选择回退重载并将value 设置为false

那么enable_if_has_bar模板使用一个默认模板参数,推导出为has_bar&lt;T&gt;::value的值。当has_bar&lt;T&gt;::value 为假时使用主模板。当has_bar&lt;T&gt;::value 为真时使用特化,在这种情况下我们知道表达式&amp;T::bar 是有效的并且可以在decltype 表达式中使用它来获取类型。

std::decay 用于将 decltype 表达式的 int&amp; 结果转换为 int。从decay 继承比使用它来定义成员要短一些,即:

typedef typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type type;

我在未计算的表达式中使用了标准实用程序declval&lt;T&gt;(),它比static_cast&lt;T*&gt;(0) 打字更短,而且更惯用和表达。

使用decay 的替代方法是另一种辅助类型,从int T::* 类型中获取int 类型,例如

template<typename T>
  struct remove_class;
  { };

template<typename Member, typename Class>
  struct remove_class<Member Class::*>
  {
    typedef Member type;
  };

template<typename T>
  struct enable_if_has_bar<T, true>
  : remove_class<decltype(&T::bar)>
  { };

remove_class这个名字不是很好,但基本上它需要一个指向数据成员的类型并给出成员的类型。)

【讨论】:

  • 不确定我是否理解这个问题,抱歉。我已经编辑了我的答案,以明确我指的是哪个 decltype 表达式,这有帮助吗?
  • 为什么我们使用std::declval&lt;T&amp;&gt; 而不是std::declval&lt;T&gt;。不是应该返回T&amp;吗?
  • @David,在declval&lt;T&amp;&gt;() 中,T 是类类型,而不是我们想要返回的类型。使用 declval&lt;T&gt;().*(&amp;T::bar) 将导致 int&amp;&amp; 仍然没有帮助。 ReturnBar 函数有一个const T&amp; 类型的参数,所以return value.bar; 是一个const int&amp;,它不能绑定到int&amp;int&amp;&amp; 的返回类型。试试看。
  • 如果意图是让ReturnBar 返回对该成员的引用,那么它可以返回const enable_if_has_bar&lt;T&gt;::type&amp;,这将起作用。 ReturnBar 可以适应使用完美转发,并将成员作为正确的值类别(右值或左值)返回,但这需要更多的工作。
【解决方案2】:

虽然 Jonathan Wakely 的回答非常好,但让我提供一个稍微不同的模式:

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//here is the use case
template<typename T, typename = void>
struct enable_if_has_bar{ };

template<typename T>
struct enable_if_has_bar<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>>
  : std::decay<decltype(std::declval<T&>().*(&T::bar))>
  { };

这是一个活生生的例子:http://ideone.com/un0ZgH

虽然这自然是一个品味问题,但我喜欢使用类型接收器模式,因为 SFINAE 测试的语法和我在元函数体中实际使用的语法完全相同并且彼此相邻。因此,它希望不易出错。

【讨论】:

    猜你喜欢
    • 2017-12-22
    • 1970-01-01
    • 2020-03-02
    • 1970-01-01
    • 2018-06-08
    • 1970-01-01
    • 1970-01-01
    • 2020-07-18
    • 1970-01-01
    相关资源
    最近更新 更多