【问题标题】:Possible to use type_traits / SFINAE to find if a class defines a member TYPE?可以使用 type_traits / SFINAE 来查找类是否定义了成员 TYPE?
【发布时间】:2012-08-05 04:22:17
【问题描述】:

我见过这个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;
}

【问题讨论】:

  • foo1 不完整——这是故意的吗?

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


【解决方案1】:

试试这个

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,这可能是获取特征的最简单方法(尽管我会避免为此编写特征,因为可以在需要的地方直接应用相同的技术)
  • 这将以 void_t 的名义成为标准
  • 我更喜欢struct has_bar final: public std::(false|true)_type,因为这样你就可以免费获得它的类型定义、转换构造函数等。
【解决方案2】:

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

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

只有当barC 的非类型成员时,子表达式&amp;C::bar 才有效。但是你需要检查的是它是否是一个type。对模板的最小更改可能是:

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

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

关于是否需要该 trait 存在一个不同的问题,因为如果您在目标模板中使用 C::bar,那么对于没有该嵌套类型的类型,该模板将被自动丢弃。


编辑

我的意思是,在您的方法中,您需要为每个可能的嵌套类型创建一个特征,只是为了生成一个包含或不包含嵌套类型的模板 (enable_if)。让我们采用不同的方法...首先我们定义一个通用实用程序来根据条件选择类型,这对于此问题不是必需,更简单的template &lt;typename T&gt; void_type { typedef void type; }; 就足够了,但是实用程序模板在其他情况下也很有用:

// 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;
};

现在只需将 SFINAE 用于类模板特化:

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&lt;foo2&gt; 时需要做什么:第一步是发现有一个模板名为target,但该模板接受两个参数,其中只提供了一个。然后它查看基本模板(未专门化的模板),发现第二个参数可以默认为void。从此时起,它将认为您的实例化为:target&lt;foo2,void&gt;(在注入默认参数之后)。它会尽量匹配最好的专业。仅考虑第二个参数 void 的特化。如果T::barvoid,您上面的模板将只能使用专用版本(您可以通过将foo2 更改为:struct foo2 { typedef void bar; } 来测试它。因为您不希望专业化启动 只有当嵌套类型为void 时,您才需要额外的模板来接受C::bar(如果类型不包含嵌套bar,则会失败)但总是 yield void 作为嵌套类型。

【讨论】:

  • 谢谢大卫。正如你所建议的,我已经能够让特征返回我想要的东西,但随后尝试在不使用特征的情况下实现这一点。我已经编辑了我的问题以反映这一点 - 您能否评论如何实现您的建议?
  • @lori : David 并不是说​​ 从不 需要一个 trait,只是在你的特定用例中可能不需要它——在许多情况下,一个 trait正是合适的解决方案,但在其他情况下,它可能是矫枉过正。例如,请参阅this demo,它不使用特征,而是使用简单的重载。如果你确实需要一个特征,this demo 会显示一个。
  • 这不适用于被非类型隐藏的类型,例如struct A { void bar(); struct bar {}; };
  • @Johannes :OTOH,我可以射击任何编写类似代码的人,所以*耸耸肩*。 :-P
  • @lori:我已经更新了答案,以说明您实际上不需要编写手工制作的特征类来检测这一点,并且通用方法将更加简洁和灵活。
【解决方案3】:

我更喜欢将它包装在宏中。

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

【讨论】:

    【解决方案4】:

    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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多