如果我们要求编译器告诉我们任何关于类类型T的信息,而这个类类型还没有被声明,那么我们一定会得到一个编译错误。因此,如果我们想知道类T是否“存在”,而T可能还没有被声明,那么我们必须首先声明T。 但这是可以的,因为仅仅声明T并不能使它“存在”,因为我们所说的 * T exists* 的意思是 * T is defined*。如果在声明了T之后,你就可以确定它是否已经被 defined 了,那么你就不必感到困惑了。 因此,问题是确定T是否是已定义的类类型。 sizeof(T)在这里没有帮助。如果T未定义,则它将给予incomplete type T错误。typeid(T)也是如此。在类型T *上创建SFINAE探测器也没有任何好处,因为只要声明了T,T * * 就是 * 已定义的类型。即使T不是,因为我们必须声明类T,std::is_class<T>也不是答案,因为该声明足以让它说“Yes”。 C++11在<type_traits>中提供了std::is_constructible<T ...Args>。这能提供现成的解决方案吗?-假设如果定义了T,那么它必须至少有一个构造函数。 恐怕不行。如果你知道T的至少一个公共构造函数的签名,那么GCC的<type_traits>(从4.6.3开始)确实可以做这件事。假设一个已知的公共构造函数是T::T(int)。那么:
#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
4条答案
按热度按时间nnvyjq4y1#
如果我们要求编译器告诉我们任何关于类类型
T
的信息,而这个类类型还没有被声明,那么我们一定会得到一个编译错误。因此,如果我们想知道类T
是否“存在”,而T
可能还没有被声明,那么我们必须首先声明T
。但这是可以的,因为仅仅声明
T
并不能使它“存在”,因为我们所说的 *T
exists* 的意思是 *T
is defined*。如果在声明了T
之后,你就可以确定它是否已经被 defined 了,那么你就不必感到困惑了。因此,问题是确定
T
是否是已定义的类类型。sizeof(T)
在这里没有帮助。如果T
未定义,则它将给予incomplete type T
错误。typeid(T)
也是如此。在类型T *
上创建SFINAE探测器也没有任何好处,因为只要声明了T
,T *
* 就是 * 已定义的类型。即使T
不是,因为我们必须声明类T
,std::is_class<T>
也不是答案,因为该声明足以让它说“Yes”。C++11在
<type_traits>
中提供了std::is_constructible<T ...Args>
。这能提供现成的解决方案吗?-假设如果定义了T
,那么它必须至少有一个构造函数。恐怕不行。如果你知道
T
的至少一个公共构造函数的签名,那么GCC的<type_traits>
(从4.6.3开始)确实可以做这件事。假设一个已知的公共构造函数是T::T(int)
。那么:如果定义了
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):唯一的限制是
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
是否在你的代码中的某个地方被定义了,方法是在问这个问题之前确保你声明了它。用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
的顶部添加以下代码:ogq8wdun2#
仍然没有找到满意的答案在这篇文章...
Mike Kinghan正确地开始了回答,并说了一件聪明的事情:
所以问题是确定T是否是一个定义的类类型。
但是
sizeof(T)在这里没有帮助
是不正确的。
下面是如何使用
sizeof(T)
实现这一点:nzk0hqpo3#
我认为name lookup技巧是实现这一点的方法。如果您不怕将名称注入库的名称空间:
如果
A
是在全局名称空间中声明的:yv5phkfx4#
好了,我想我找到了一种方法,尽管可能有更好的方法。假设我们有一个类A,它包含在库的某些示例中,而不包含在其他示例中。诀窍是在A中定义一个特殊的私有转换构造函数,然后使用SFINAE来检测转换构造函数。当包含A时,检测成功;如果不是,则检测失败。
下面是一个具体的例子,首先是检测模板的头文件class_defined.hpp:
现在是一个包含类定义的头文件,blah.hpp:
现在,源文件main.cpp:
如果编译时定义了BLAH_INCLUDED,则打印1。如果没有定义BLAH_INCLUDED,则打印0。不幸的是,这两种情况下编译时仍然需要类的前向声明。我看不到避免这种情况的方法。