可以使用type_traits / SFINAE来查找类是否定义成员TYPE?

时间:2012-08-05 04:22:18

标签: c++ c++11 template-meta-programming sfinae

我已经看到这个question允许人们检查是否存在成员函数,但我试图找出一个类是否有成员类型

在下面的示例中,两者都评估为“false”,但我想找到一种方法,以便has_bar<foo1>::value评估为false,has_bar<foo2>::value评估为 true

这可能吗?

#include <iostream>

struct foo1;
struct foo2 { typedef int bar; };

template <typename T>
class has_bar
{
    typedef char yes;
    typedef long no;

    template <typename C> static yes check( decltype(&C::bar) ) ;
    template <typename C> static no  check(...);
public:
    enum { value = sizeof(check<T>(0)) == sizeof(yes) };
};

int main()
{
    std::cout << has_bar<foo1>::value << std::endl;
    std::cout << has_bar<foo2>::value << std::endl;
    return 0;
}

编辑:针对以下答案实施专业化:

  

...如果您在目标模板中使用C :: bar,则模板将是   对于没有嵌套类型的类型自动丢弃。

我试过这样做,但显然遗漏了一些东西

#include <iostream>

struct foo1;
struct foo2 { typedef int bar; };

template <typename T, typename U = void>
struct target
{
    target()
    {
        std::cout << "default target" << std::endl;
    }
};

template<typename T>
struct target<T, typename T::bar>
{
    target()
    {
        std::cout << "specialized target" << std::endl;
    }
};

int main()
{
    target<foo1>();
    target<foo2>();
    return 0;
}

4 个答案:

答案 0 :(得分:14)

试试这个

template<class T>
struct Void {
  typedef void type;
};

template<class T, class U = void>
struct has_bar {
    enum { value = 0 };
};

template<class T>
struct has_bar<T, typename Void<typename T::bar>::type > {
    enum { value = 1 };
};

答案 1 :(得分:13)

您无法获取指向类型成员的成员的指针:

template <typename C> static yes check( decltype(&C::bar) ) ;

仅当&C::barbar的非类型成员时,子表达式C才有效。但您需要检查的是它是否是类型。对模板的最小更改可能是:

template <typename C> static yes check( typename C::bar* ) ;

如果barC的嵌套类型,那么该函数重载将是一个有效的候选者(0将是指向任何C::bar类型的指针),但如果{ {1}}不包含嵌套的C,然后它将被丢弃,第二次测试将是唯一的候选者。

关于是否需要特征存在一个不同的问题,因为如果在目标模板中使用bar,则对于没有嵌套类型的类型,模板将自动被丢弃。 / p>


修改

我的意思是,在您的方法中,您需要为每个可能的嵌套类型创建特征,只是为了生成一个包含或不包含嵌套类型的模板(C::bar)。让我们采取不同的方法......首先,我们定义一个通用实用程序来根据条件选择一个类型,对于这个问题,这不是 required ,而更简单的enable_if就足够了,但是实用程序模板在其他情况下很有用:

template <typename T> void_type { typedef void type; };

现在只需要使用SFINAE进行类模板特化:

// General utility: if_<Condition, Then, Else>::type
// Selects 'Then' or 'Else' type based on the value of 
// the 'Condition'
template <bool Condition, typename Then, typename Else = void>
struct if_ {
   typedef Then type;
};
template <typename Then, typename Else>
struct if_<false, Then, Else > {
   typedef Else type;
};

请注意,与您的方法的主要区别在于使用额外的中间模板(替换将失败并且不是错误的模板)产生template <typename T, typename _ = void> struct target { // generic implementation }; template <typename T> struct target<T, typename if_<false,typename T::bar>::type> { // specialization for types holding a nested type `T::bar` }; 类型(成功时)。这就是为什么上面的void模板也可以工作的原因:你只需要使用嵌套类型作为模板的参数,并且让它失败,你不关心模板的作用,只要如果成功,评估是嵌套的void_type(必须是type)。

如果不明显(我最初并不是这样)为什么你的方法不起作用,请考虑编译器在遇到void时需要做什么:第一步是找到那里是一个名为target<foo2>的模板,但该模板有两个参数,其中只提供了一个参数。然后它在基本模板(非专用的模板)中查找并发现第二个参数可以默认为target。从这一点开始,它会将您的实例化视为:void(在注入默认参数之后)。它会尝试匹配最好的专业化。仅考虑第二个参数 target<foo2,void>的特化。如果voidT::bar,您上面的模板将只能使用专用版本(您可以通过将void更改为:foo2进行测试。因为您不想要当嵌套类型为struct foo2 { typedef void bar; }时,需要使用<{1}}的额外模板来启用 的专门化(如果类型不包含嵌套的{{}则会失败1}})但总是void作为嵌套类型。

答案 2 :(得分:2)

我更喜欢用宏包装它。

test.h:

#include <type_traits>

template<typename ...>
struct void_type
{
     using type = void;
};

template<typename ...T>
using void_t = typename void_type<T...>::type;

#define HAS_TYPE(NAME) \
template<typename, typename = void> \
struct has_type_##NAME: std::false_type \
{}; \
template<typename T> \
struct has_type_##NAME<T, void_t<typename T::NAME>>: std::true_type \
{} \

HAS_TYPE(bar);

TEST.CPP:

#include <iostream>

struct foo1;
struct foo2 { typedef int bar; };

int main()
{
    std::cout << has_type_bar<foo1>::value << std::endl;
    std::cout << has_type_bar<foo2>::value << std::endl;
    return 0;
}

答案 3 :(得分:1)

C++20 更新:

现在检查给定类型是否包含特定类型定义变得更加容易。

template<typename T>
concept has_bar = requires {
    typename  T::bar;
};

...所以您的示例代码演变为:

#include <iostream>

struct foo1;
struct foo2 { typedef int bar; };

template <typename T, typename U = void>
struct target
{
    target()
    {
        std::cout << "default target" << std::endl;
    }
};

template<typename T>
requires(has_bar<T>)
struct target<T>
{
    target()
    {
        std::cout << "specialized target" << std::endl;
    }
};

int main()
{
    target<foo1>();
    target<foo2>();
    return 0;
}

gcc.godbolt 上的示例:https://gcc.godbolt.org/z/a15G13

相关问题