SFINAE会员通过

SFINAE Member Pass through

本文关键字:SFINAE      更新时间:2023-10-16

我想知道是否有可能创建一个类,作为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的精细艺术也不是很精通。也许有人可以改进/修理它?因为我很迷茫

我通常更喜欢创建自定义的enable_if风格类型,因为我发现使用单个trait类型比使用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,即true。否则将选择回退过载,并将value设置为false

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

std::decay用于将decltype表达式的int&结果变为int。从decay继承比使用它来定义成员要短一些,后者将是:

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

我在未求值的表达式中使用了标准实用程序declval<T>(),它比static_cast<T*>(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不是很好,但基本上它接受指向数据成员类型的指针,并给出成员的类型)

虽然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测试的语法和我在元函数体中实际使用的语法完全相同并且彼此相邻。因此,希望它不那么容易出现bug。