如何使用SFINAE检测一个类的存在?

时间:2012-05-23 00:32:26

标签: c++ templates sfinae

是否可以使用SFINAE检测C ++中是否存在类?如果可能的话怎么样?

假设我们有一个只由某些版本的库提供的类。我想知道是否可以使用SFINAE来检测该类是否存在。检测结果是任意的,比如枚举常数,如果存在,则为1,否则为0。

4 个答案:

答案 0 :(得分:34)

如果我们要求编译器告诉我们关于没有的类类型T的任何信息 甚至被宣布我们必然会得到编译错误。没有办法 在那附近。因此,如果我们想知道班级T是否“存在”,那么T 甚至可能尚未宣布,我们必须首先声明T

但这没关系,因为只是声明T不会使它“存在”,因为 我们必须通过 T存在来定义 T 。如果,宣布T, 然后,您可以确定它是否已经已定义,您无需进入 任何困惑。

所以问题是确定T是否是定义的类类型。

sizeof(T)在这里没有帮助。如果T未定义,那么它将给出一个 incomplete type T错误。同样typeid(T)。也没有任何好处 在类型T *上制作SFINAE探针,因为T * 定义的类型 只要T已声明,即使T不是。因为我们是 有义务宣布课程Tstd::is_class<T>不是 或者回答,因为该声明足以说“是”。

C ++ 11在std::is_constructible<T ...Args>中提供<type_traits>。能够 这提供了一个现成的解决方案? - 如果定义了T,那么它必须至少有一个构造函数。

我不敢。如果你知道至少一个公众的签名 T的构造函数然后是GCC的<type_traits>(截至4.6.3)确实会这样做 这生意。假设一个已知的公共构造函数是T::T(int)。然后:

std::is_constructible<T,int>::value
如果定义T,则

为真,如果仅声明T则为false。

但这不便携。 VC ++ 2010中的<type_traits>尚未提供std::is_constructible,如果std::has_trivial_constructor<T>未定义,则其T甚至会为barf:最有可能在std::is_constructible到达时跟风。此外,在T中只有std::is_constructible的私人构造者可以提供给T甚至GCC 将barf(眉毛抬起)。

如果定义了T,它必须有一个析构函数,并且只有一个析构函数。并且该析构函数比T::~T的任何其他可能成员更有可能公开。从这个角度来看,我们可以做的最简单和最强大的游戏是为T的存在制作一个SFINAE探测器。

此SFINAE探针无法以常规方式确定 mf是否具有普通成员函数&T::mf - 使“是超载” SFINAE探测函数的一个参数是用术语定义的 T 的类型。因为我们不允许带一个地址 析构函数(或构造函数)。

尽管如果定义了T::~T,那么DT的类型为decltype(dt) - 必须是 每当dt是一个计算结果为的表达式时,由T::~T产生 调用DT *;因此,#ifndef HAS_DESTRUCTOR_H #define HAS_DESTRUCTOR_H #include <type_traits> /*! The template `has_destructor<T>` exports a boolean constant `value that is true iff `T` has a public destructor. N.B. A compile error will occur if T has non-public destructor. */ template< typename T> struct has_destructor { /* Has destructor :) */ template <typename A> static std::true_type test(decltype(std::declval<A>().~A()) *) { return std::true_type(); } /* Has no destructor :( */ template<typename A> static std::false_type test(...) { return std::false_type(); } /* This will be either `std::true_type` or `std::false_type` */ typedef decltype(test<T>(0)) type; static const bool value = type::value; /* Which is it? */ }; #endif // EOF 也将成为一种类型 原则作为函数重载的参数类型给出。所以我们 可以像这样编写探针(GCC 4.6.3):

T

只有decltype(std::declval<A>().~A())必须在has_destructor<T>的参数表达式中合法调用 public 析构函数的限制。 (std::declval<A>().~A()是对我提供的方法 - 内省模板here的简化改编。)

该参数表达式std::declval<A>()的含义可能对某些人来说是模糊的,特别是std::declval<T>()。函数模板<type_traits>T&&中定义并返回T(对decltype的rvalue-reference) - 尽管它只能在未评估的上下文中调用,例如参数std::declval<A>().~A()。因此~A()的含义是在给定A 时调用std::declval<A>()T通过避免需要A的任何公共构建者或让我们了解它来为我们提供良好的服务。

因此,“Yes overload”的SFINAE探测器的参数类型是:指向test<T>(0) 的析构函数类型的指针,A将匹配如果有A 的析构函数类型,则为T = has_destructor<T>

掌握T - 并牢记其对T的公开可破坏值的限制 - 您可以通过确保代码中的某个点定义类#include "has_destructor.h" #include <iostream> class bar {}; // Defined template< class CharT, class Traits > class basic_iostream; //Defined template<typename T> struct vector; //Undefined class foo; // Undefined int main() { std::cout << has_destructor<bar>::value << std::endl; std::cout << has_destructor<std::basic_iostream<char>>::value << std::endl; std::cout << has_destructor<foo>::value << std::endl; std::cout << has_destructor<vector<int>>::value << std::endl; std::cout << has_destructor<int>::value << std::endl; std::count << std::has_trivial_destructor<int>::value << std::endl; return 0; } 在提出问题之前你宣布它。这是一个测试程序。

// Defined

使用GCC 4.6.3构建,这将告诉您2 // Undefined个类 有析构函数,2 int类没有。第五 输出线会说std::has_trivial_destructor<int>是可破坏的,最终的 该行将显示std::is_class<T>同意。如果我们想要 要将字段缩小到类类型,可以在之后应用T 我们确定std::declval()是可破坏的。

Visual C ++ 2010不提供has_destructor.h。支持该编译器 您可以在#ifdef _MSC_VER namespace std { template <typename T> typename add_rvalue_reference<T>::type declval(); } #endif 的顶部添加以下内容:

{{1}}

答案 1 :(得分:11)

在这篇文章中仍未找到令人满意的答案......

迈克金汉开始正确的答案,并说了一个聪明的事情:

  

所以问题是确定T是否是定义的类类型。

但是

  

sizeof(T)在这里没有帮助

不正确......

以下是使用template <class T, class Enable = void> struct is_defined { static constexpr bool value = false; }; template <class T> struct is_defined<T, std::enable_if_t<(sizeof(T) > 0)>> { static constexpr bool value = true; };

执行此操作的方法
 ID    Date        
 ------------------
 20    03/07/2017                        
 30    03/07/2017                                                    
 30    17/07/2017                            
 40    24/07/2017                            
 50    24/07/2017   

答案 2 :(得分:6)

使用SFINAE,没有。我认为名称查找技巧是完成这项工作的方法。如果您不害怕将名称注入库的命名空间:

namespace lib {
#if DEFINE_A
class A;
#endif
}

namespace {
    struct local_tag;
    using A = local_tag;
}

namespace lib {
    template <typename T = void>
    A is_a_defined();
}

constexpr bool A_is_defined =
  !std::is_same<local_tag, decltype(lib::is_a_defined())>::value;

<强> Demo.

如果在全局命名空间中声明了A

#if DEFINE_A
class A;
#endif

namespace {
    struct local_tag;
    using A = local_tag;
}

namespace foo {
    template <typename T = void>
    ::A is_a_defined();
}

constexpr bool A_is_defined =
  !std::is_same<local_tag, decltype(foo::is_a_defined())>::value;

<强> Demo.

答案 3 :(得分:0)

好吧,我想我找到了一种方法来做到这一点,尽管可能有更好的方法。假设我们有A类,它包含在库的某些实例中而不包含在其他实例中。诀窍是在A中定义一个特殊的私有转换构造函数,然后使用SFINAE来检测转换构造函数。当包括A时,检测成功;当它不是时,检测失败。

这是一个具体的例子。首先是检测模板的标题,class_defined.hpp:

struct class_defined_helper { };

template< typename T >
struct class_defined {

  typedef char yes;
  typedef long no;

  static yes test( T const & );
  static no  test( ... );

  enum { value = sizeof( test( class_defined_helper( )) == sizeof( yes ) };
};

#define CLASS_DEFINED_CHECK( type )     \
  type( class_defined_helper const & ); \
                                        \
  friend struct class_defined< type >;

现在是一个包含类定义的标题,blah.hpp:

#include "class_defined.hpp"

#ifdef INCLUDE_BLAH
class blah {
  CLASS_DEFINED_CHECK( blah );
};
#else
class blah;
#endif

现在是源文件main.cpp:

#include "blah.hpp"

int main( ) {
  std::cout << class_defined< blah >::value << std::endl;
}

使用BLAH_INCLUDED编译定义此打印1.如果没有定义BLAH_INCLUDED,则打印0.不幸的是,这仍然需要在两种情况下都要编译类的前向声明。我没有办法避免这种情况。