如何使用 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
不是.既然我们是必须声明类 T
,std::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_constructor
T
未定义:很可能当 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
相关文章