c++ 如何使用SFINAE检测类的存在?

gkl3eglg  于 2023-02-20  发布在  其他
关注(0)|答案(4)|浏览(179)

是否可以使用SFINAE检测C++中是否存在类?如果可能,如何检测?
假设我们有一个类,它只由某个库的某些版本提供,我想知道是否可以使用SFINAE来检测这个类是否存在,检测的结果是任意的,比如说一个枚举常量,如果它存在,它就是1,否则就是0。

nnvyjq4y

nnvyjq4y1#

如果我们要求编译器告诉我们任何关于类类型T的信息,而这个类类型还没有被声明,那么我们一定会得到一个编译错误。因此,如果我们想知道类T是否“存在”,而T可能还没有被声明,那么我们必须首先声明T
但这是可以的,因为仅仅声明T并不能使它“存在”,因为我们所说的 * T exists* 的意思是 * T is defined*。如果在声明了T之后,你就可以确定它是否已经被 defined 了,那么你就不必感到困惑了。
因此,问题是确定T是否是已定义的类类型。
sizeof(T)在这里没有帮助。如果T未定义,则它将给予incomplete type T错误。typeid(T)也是如此。在类型T *上创建SFINAE探测器也没有任何好处,因为只要声明了TT * * 就是 * 已定义的类型。即使T不是,因为我们必须声明类Tstd::is_class<T>也不是答案,因为该声明足以让它说“Yes”。
C++11在<type_traits>中提供了std::is_constructible<T ...Args>。这能提供现成的解决方案吗?-假设如果定义了T,那么它必须至少有一个构造函数。
恐怕不行。如果你知道T的至少一个公共构造函数的签名,那么GCC的<type_traits>(从4.6.3开始)确实可以做这件事。假设一个已知的公共构造函数是T::T(int)。那么:

std::is_constructible<T,int>::value

如果定义了T,则为true;如果仅声明了T,则为false。
但是这是不可移植的。VC++ 2010中的<type_traits>还没有提供std::is_constructible,如果没有定义T,甚至它的std::has_trivial_constructor<T>也会出错:很可能当std::is_constructible到达时,它也会跟随。此外,在T的私有构造函数只存在的情况下,提供给std::is_constructible,那么即使GCC也会呕吐(这是令人惊讶的)。
如果定义了T,那么它必须有一个析构函数,而且只有一个析构函数,而且这个析构函数比T的任何其他可能的成员都更可能是公共的,因此,我们能做的最简单和最有力的事情就是为T::~T的存在设计一个SFINAE探测器。
无法以常规方式构建此SFINAE探测器,以确定T是否具有普通成员函数mf-使SFINAE探测器函数的“Yes重载”采用根据 * &T::mf的类型 * 定义的参数。因为不允许我们采用析构函数(或构造函数)的地址。
然而,如果定义了T,则T::~T具有类型DT-只要dt是求值为T::~T的调用的表达式,就必须由decltype(dt)产生;因此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必须有一个 public 析构函数,才能在decltype(std::declval<A>().~A())的参数表达式中合法调用(has_destructor<T>是我提供的方法内省模板here的简化改编版)。
参数表达式std::declval<A>().~A()的含义可能对某些人来说很模糊,特别是std::declval<A>()。函数模板std::declval<T>()<type_traits>中定义,并返回T&&(右值-对X1 M55 N1 X的引用)-尽管它只能在未评估的上下文中调用,比如decltype的参数。所以std::declval<A>().~A()的意思是 * 在某个给定的A上调用~A()std::declval<A>()在这里很好地满足了我们的需求,因为它避免了任何T的公共构造函数的需要,或者让我们知道。
因此,“Yes重载”的SFINAE探测器的参数类型为:
指向A * 的析构函数的类型的指针,并且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;
}

用GCC 4.6.3构建的,它会告诉你2个// Defined类有析构函数,2个// Undefined类没有。输出的第五行会说int是可析构的,最后一行会显示std::has_trivial_destructor<int>同意。如果我们想把字段缩小到类类型,可以在我们确定T是可析构的之后应用std::is_class<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
ogq8wdun

ogq8wdun2#

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

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

nzk0hqpo3#

我认为name lookup技巧是实现这一点的方法。如果您不怕将名称注入库的名称空间:

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;
    • 第一个e第一个f第一个x

如果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;
    • 一个月一次**
yv5phkfx

yv5phkfx4#

好了,我想我找到了一种方法,尽管可能有更好的方法。假设我们有一个类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。不幸的是,这两种情况下编译时仍然需要类的前向声明。我看不到避免这种情况的方法。

相关问题