SFINAE会员通过

时间:2012-12-30 23:45:36

标签: c++ templates sfinae

我想知道是否有可能创建一个类作为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的艺术不太熟悉。也许有人可以改进/修复它?因为我不知所措。

2 个答案:

答案 0 :(得分:4)

我通常更喜欢按照您的尝试创建自定义enable_if - 样式类型,因为我发现使用单个特征类型而不是enable_if<some_trait<T>, another_trait<T>>的组合可以更清楚地阅读代码。但在这种情况下,您的代码中存在一些问题会阻止其正常工作。

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

您的decltype(static_cast<T*>(0)->*static_cast<decltype(&T::bar)>(0))表达式会导致您int&而不是int。这是因为decltype(foobar.*(&foo::bar))相当于decltype(foobar.bar)foobar.bar是左值,因此decltypeint&。函数ReturnBar如果返回int&将无法编译,因为参数value是const,因此您无法将value.bar绑定到非const int& }。

这是一个有效的版本:

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))>
  { };

这首先声明帮助器has_bar来回答类型是否具有嵌套成员的问题。该助手使用SFINAE获得truefalse值。如果&T::bar是有效表达式,则将使用test的第一个重载,返回true_type,因此value将设置为true_type::value,即{{ 1}}。否则,将选择回退重载并将true设置为value

然后false模板使用默认模板参数,该参数推导为enable_if_has_bar的值。 has_bar<T>::value为false时使用主模板。当has_bar<T>::value为真时使用特化,在这种情况下,我们知道表达式has_bar<T>::value是有效的,并且可以在decltype表达式中使用它来获取类型。

&T::bar用于将decltype表达式的std::decay结果转换为int&。继承自int比使用它来定义成员要短一些,这将是:

decay

我在未评估的表达式中使用了标准实用程序typedef typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type type; ,这种表达式比declval<T>()更短一些,更具惯用性和表达性。

使用static_cast<T*>(0)的替代方法是从类型decay获取类型int的另一种帮助类型,例如。

int T::*

(名称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)> { }; 不是很好,但基本上它采用指向数据成员的类型并给出成员的类型。)

答案 1 :(得分:1)

虽然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测试的语法和我在元函数体中实际使用的语法完全相同并且彼此相邻。因此,希望减少错误。