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

2021-12-13 00:00:00 templates c++ sfinae

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

Is it possible to detect if a class exists in C++ using SFINAE? If possible then how?

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

Suppose we have a class that is provided only by some versions of a library. I'd like to know if it is possible to use SFINAE to detect whether the class exists or not. The result of detection is arbitrary, say an enum constant which is 1 if it exists, 0 otherwise.

推荐答案

如果我们要求编译器告诉我们任何关于 T 的类类型即使被声明我们也一定会得到一个编译错误.没有办法围绕那个.因此,如果我们想知道 T 类是否存在",其中 T甚至可能还没有被声明,我们必须先声明T.

If we ask the compiler to tell us anything about a class type T that has not even been declared we are bound to get a compilation error. There is no way around that. Therefore if we want to know whether class T "exists", where T might not even have been declared yet, we must declare T first.

但这没关系,因为仅仅声明 T 不会让它存在",因为我们所说的 T 存在 是 T 已定义.如果已经声明了 T,然后你可以确定它是否已经定义,你不需要在任何混淆.

But that is OK, because merely declaring T will not make it "exist", since what we must mean by T exists is T is defined. And if, having declared T, you can then determine whether it is already defined, you need not be in any confusion.

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

So the problem is to determine whether T is a defined class type.

sizeof(T) 在这里没有帮助.如果 T 未定义,那么它会给出一个类型不完整错误.同样typeid(T).也没什么好处在 T * 类型上制作 SFINAE 探针,因为 T * 是一个定义的类型只要 T 已经声明,即使 T 不是.既然我们是必须声明类 Tstd::is_class 不是要么回答,因为该声明足以让它说是".

sizeof(T) is no help here. If T is undefined then it will give an incomplete type T error. Likewise typeid(T). Nor is it any good crafting an SFINAE probe on the type T *, because T * is a defined type as long as T has been declared, even if T isn't. And since we are obliged to have a declaration of class T, std::is_class<T> is not the answer either, because that declaration will suffice for it to say "Yes".

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

C++11 provides std::is_constructible<T ...Args> in <type_traits>. Can this offer an off-the-peg solution? - given that if T is defined, then it must have at least one constructor.

恐怕不行.如果你知道至少一个公众的签名T 的构造函数,然后 GCC 的 (从 4.6.3 开始)确实可以这生意.假设一个已知的公共构造函数是 T::T(int).然后:

I'm afraid not. If you know the signature of at least one public constructor of T then GCC's <type_traits> (as of 4.6.3) will indeed do the business. Say that one known public constructor is T::T(int). Then:

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

如果定义了 T 则为真,如果仅声明 T 则为假.

will be true if T is defined and false if T is merely declared.

但这不是便携式的.VC++ 2010 中的 还没有提供 std::is_constructible 甚至它的 std::has_trivial_constructorT 未定义:很可能当 std::is_constructible 确实到达时它会效仿.此外,如果只有 T 的私有构造函数存在用于提供给 std::is_constructible 那么甚至 GCC会吐(这是扬眉).

But this isn't portable. <type_traits> in VC++ 2010 doesn't yet provide std::is_constructible and even its std::has_trivial_constructor<T> will barf if T is not defined: most likely when std::is_constructible does arrive it will follow suit. Furthermore, in the eventuality that only private constructors of T exist for offering to std::is_constructible then even GCC will barf (which is eyebrow raising).

如果定义了T,它必须有一个析构函数,并且只有一个析构函数.并且该析构函数比 T 的任何其他可能成员更可能是公开的.有鉴于此,我们可以做的最简单和最强大的方法是为 T::~T 的存在制作一个 SFINAE 探测器.

If T is defined, it must have a destructor, and only one destructor. And that destructor is likelier to be public than any other possible member of T. In that light, the simplest and strongest play we can make is to craft an SFINAE probe for the existence of T::~T.

这个 SFINAE 探针不能用常规方法制作来确定T 是否有普通成员函数 mf - 使Yes 重载"SFINAE 探针函数的参数采用以术语定义的参数&T::mf 的类型.因为我们不允许取地址析构函数(或构造函数).

This SFINAE probe cannot be crafted in the routine way for determining whether T has an ordinary member function mf - making the "Yes overload" of the SFINAE probe function takes an argument that is defined in terms of the type of &T::mf. Because we're not allowed to take the address of a destructor (or constructor).

然而,如果定义了 T,则 T::~T 有一个类型 DT- 它必须是每当 dt 是一个计算结果为T::~T 的调用;因此 DT * 也将是一个类型,可以在原则是作为函数重载的参数类型给出的.因此我们可以这样写探针(GCC 4.6.3):

Nevertheless, if T is defined, then T::~T has a type DT- which must be yielded by decltype(dt) whenever dt is an expression that evaluates to an invocation of T::~T; and therefore DT * will be a type also, that can in principle be given as the argument type of a function overload. Therefore we can write the probe like this (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 是我在此处贡献的方法内省模板的简化改编版.)

with only the restriction that T must have a public destructor to be legally invoked in the argument expression of decltype(std::declval<A>().~A()). (has_destructor<T> is a simplified adaptation of the method-introspecting template I contributed here.)

该参数表达式的含义 std::declval().~A() 可能对某些人来说是模糊的,特别是 std::declval().函数模板 std::declval() 中定义并返回一个 T&&(右值-引用 T) - 尽管它只能在未求值的上下文中调用,例如 decltype 的参数.所以 std::declval().~A() 的含义是在给定的 A 上调用 ~A().std::declval<A>() 在这里为我们提供了很好的服务,无需任何 T 的公共构造函数,或者让我们了解它.

The meaning of that argument expression std::declval<A>().~A() may be obscure to some, specifically std::declval<A>(). The function template std::declval<T>() is defined in <type_traits> and returns a T&& (rvalue-reference to T) - although it may only be invoked in unevaluated contexts, such as the argument of decltype. So the meaning of std::declval<A>().~A() is a call to ~A() upon some given A. std::declval<A>() serves us well here by obviating the need for there to be any public constructor of T, or for us to know about it.

相应地,Yes 重载"的 SFINAE 探测器的参数类型为:指向A 的析构函数类型的指针,test<;T>(0) 将匹配该重载,以防万一有这样的类型作为 A 的析构函数,对于 A =T.

Accordingly, the argument type of the SFINAE probe for the "Yes overload" is: pointer to the type of the destructor of A, and test<T>(0) will match that overload just in case there is such a type as destructor of A, for A = T.

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

With has_destructor<T> in hand - and its limitation to publicly destructible values of T firmly in mind - you can test whether a class T is defined at some point in your code by ensuring that you declare it before asking the question. Here is a test program.

#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 同意.如果我们想要要将字段缩小到类类型,可以在之后应用 std::is_class我们确定 T 是可破坏的.

Built with GCC 4.6.3, this will tell you that the 2 // Defined classes have destructors and the 2 // Undefined classes do not. The fifth line of output will say that int is destructible, and the final line will show that std::has_trivial_destructor<int> agrees. If we want to narrow the field to class types, std::is_class<T> can be applied after we determine that T is destructible.

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

Visual C++ 2010 does not provide std::declval(). To support that compiler you can add the following at the top of has_destructor.h:

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

相关文章