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

vit*_*aut 27 c++ templates sfinae

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

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

Mik*_*han 36

如果我们要求编译器告诉我们关于T甚至没有声明的类类型的任何信息,我们必然会遇到编译错误.没有办法绕过那个.因此,如果我们想知道类是否T"存在",T 甚至可能尚未宣布,我们必须先声明T.

但是,这是确定的,因为仅仅是宣布T将不会使其"存在",因为我们必须意味着T存在T被定义.如果,在声明之后T,您可以确定它是否已经定义,您不必担心任何混乱.

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

T这里没有帮助.如果sizeof(T)未定义则会 T出错.同样地incomplete type T.这也不是什么好各具特色的类型的SFINAE探头typeid(T),因为T * 在定义的类型,只要T *已经宣布,即使T是没有的.既然我们有义务宣布上课T,T也不是答案,因为这个声明足以让它说"是".

C++ 11提供std::is_class<T>std::is_constructible<T ...Args>.这可以提供一个现成的解决方案吗?- 假定if <type_traits>已定义,那么它必须至少有一个构造函数.

我不高兴.如果您知道T当时GCC T(截至4.6.3)的至少一个公共构造函数的签名确实会开展业务.假设一个已知的公共构造函数是<type_traits>.然后:

std::is_constructible<T,int>::value
Run Code Online (Sandbox Code Playgroud)

如果T::T(int)定义则为true,如果T仅声明则为false .

但这不便携.T在VC++ 2010中还没有提供 <type_traits>甚至它的std::is_constructible意志如果std::has_trivial_constructor<T>没有定义:最有可能何时T到达它将跟随.此外,在可能的情况下,只有私人建设者std::is_constructible提供给T当时甚至GCC将barf(这是眉毛提高).

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

此SFINAE探针无法以常规方式制作,以确定是否T::~T具有普通成员函数T- 使SFINAE探测函数的"是重载"采用根据类型mf定义的参数.因为我们不允许使用析构函数(或构造函数)的地址.

然而,如果&T::mf被定义,则T有一个类型T::~T-它必须由产生DT每当decltype(dt)是取值为的调用的表达dt; 因此也T::~T将是一个类型,原则上可以作为函数重载的参数类型给出.因此我们可以像这样编写探针(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
Run Code Online (Sandbox Code Playgroud)

只有DT *必须在参数表达式中合法调用公共析构函数的限制T.(这decltype(std::declval<A>().~A())是我在这里贡献的方法 - 内省模板的简化改编.)

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

因此,"Yes overload"的SFINAE探测器的参数类型是: 指向析构函数类型的指针T,并且A只有在存在类型为析构函数的test<T>(0)情况下才匹配该重载,for A=A

随着T在手-和它的局限性公开的破坏值has_destructor<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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

#ifdef _MSC_VER
namespace std {
template <typename T>
typename add_rvalue_reference<T>::type declval();
}
#endif
Run Code Online (Sandbox Code Playgroud)


Win*_*lds 14

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

迈克金汉开始正确的答案并告诉一个聪明的事情:

所以问题是确定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;
};
Run Code Online (Sandbox Code Playgroud)

  • @IsaacPascual是的,这就是它起作用的原因:当类型不完整时,第二次替换失败。实际上,可以将其重命名为“ is_complete”,但就类而言,它只是“ is_defined”。顺便说一句,我刚刚意识到一年之后,`sizeof(T)&gt; 0`可以简单地由`sizeof(T)`代替。 (2认同)

Cas*_*sey 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;
Run Code Online (Sandbox Code Playgroud)

演示.

如果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;
Run Code Online (Sandbox Code Playgroud)

演示.