32

是否可以使用SFINAE检测 C++ 中是否存在一个类?如果可能,那怎么办?

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

4

4 回答 4

40

T如果我们要求编译器告诉我们关于一个甚至没有被声明的类类型的任何信息,我们一定会得到一个编译错误。没有办法解决这个问题。因此,如果我们想知道类是否T“存在”,T 甚至可能还没有声明,我们必须先声明T

但这没关系,因为仅仅声明T不会使它“存在”,因为我们必须通过T存在的意思是T已定义。如果在声明T之后,您可以确定它是否已经定义,那么您不必感到困惑。

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

sizeof(T)在这里没有帮助。如果T是未定义的,那么它会给出一个 incomplete type T错误。同样typeid(T)。对 type 进行 SFINAE 探测也没有任何好处T *,因为只要声明了T * 它就是T定义的类型,即使T不是。而且由于我们有义务声明 class Tstd::is_class<T>这也不是答案,因为该声明足以让它说“是”。

C++11std::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仅声明为假。

但这不是便携式的。<type_traits>在 VC++ 2010 中还没有提供,如果没有定义std::is_constructible它甚至会失败:很可能什么时候到达它会效仿。此外,如果只存在提供给GCC的私有构造函数,那么即使是 GCC 也会呕吐(这是令人震惊的)。std::has_trivial_constructor<T>Tstd::is_constructibleTstd::is_constructible

如果T定义了,它必须有一个析构函数,并且只有一个析构函数。并且该析构函数比任何其他可能的T. 有鉴于此,我们可以做的最简单和最强大的游戏就是为T::~T.

此 SFINAE 探针无法以常规方式制作以确定是否T具有普通成员函数mf- 使 SFINAE 探针函数的“是重载”采用根据&T::mf. 因为我们不允许获取析构函数(或构造函数)的地址。

然而,如果T定义了,则T::~T有一个类型DT- 必须由decltype(dt)只要dt是一个计算结果为T::~T;的表达式的表达式产生。因此也DT *将是一种类型,原则上可以作为函数重载的参数类型给出。因此我们可以这样编写探针(GCC 4.6.3):

#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

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

该参数表达式的含义std::declval<A>().~A()可能对某些人来说是模糊的,特别是std::declval<A>()。函数模板std::declval<T>()在中定义<type_traits>并返回一个T&&(rvalue-reference to T) - 尽管它只能在未计算的上下文中调用,例如decltype. 所以 的 意思std::declval<A>().~A()对某些给定的调用~A()Astd::declval<A>()通过消除对 的任何公共构造函数的需要T或让我们知道它的需要,在这里为我们提供了很好的服务。

因此,“Yes 重载”的 SFINAE 探测器的参数类型是:指向 的析构函数类型的指针A,并且test<T>(0)将匹配该重载,以防万一存在, for =的析构函数A这样的类型。AT

有了in hand - 并且牢记has_destructor<T>它对可公开破坏的值的限制- 您可以通过确保在提出问题之前声明它来测试是否在代码中的某个位置定义了一个类。这是一个测试程序。TT

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

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

Visual C++ 2010 不提供std::declval(). 要支持该编译器,您可以在顶部添加以下内容has_destructor.h

#ifdef _MSC_VER
namespace std {
template <typename T>
typename add_rvalue_reference<T>::type declval();
}
#endif
于 2012-05-23T15:11:30.050 回答
20

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

迈克·金汉( Mike Kinghan)正确地开始了答案,并说了一件聪明的事:

所以问题是要判断 T 是否是一个已定义的类类型。

sizeof(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;
};
于 2017-08-09T15:04:16.750 回答
9

有了 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;

演示。

如果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;

演示。

于 2014-11-11T23:04:27.257 回答
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。不幸的是,这仍然需要类的前向声明才能在两种情况下编译。我没有办法避免这种情况。

于 2012-05-23T01:45:54.910 回答