3

我想知道是否可以创建一个作为 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 的美术还不够精通。也许有人可以改进/修复它?因为我很茫然。

4

2 回答 2

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))is 等价于decltype(foobar.bar)andfoobar.bar是一个左值,所以decltypeis int&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值。if&T::bar是一个有效的表达式,那么将使用第一个重载 of test,它返回true_type,因此value将设置为true_type::valueie true。否则,将选择后备过载并将其value设置为false

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

std::decay用于将int&decltype 表达式的结果转换为 just 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)>
  { };

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

于 2012-12-31T00:13:32.233 回答
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 测试的语法和我在元函数体中实际使用的语法完全相同并且彼此相邻。因此,它希望不易出错。

于 2014-08-11T11:22:39.420 回答