是否可以使用SFINAE检测C ++中是否存在类?如果可能的话怎么样?
假设我们有一个只由某些版本的库提供的类。我想知道是否可以使用SFINAE来检测该类是否存在。检测结果是任意的,比如枚举常数,如果存在,则为1,否则为0。
答案 0 :(得分:34)
如果我们要求编译器告诉我们关于没有的类类型T
的任何信息
甚至被宣布我们必然会得到编译错误。没有办法
在那附近。因此,如果我们想知道班级T
是否“存在”,那么T
甚至可能尚未宣布,我们必须首先声明T
。
但这没关系,因为只是声明T
不会使它“存在”,因为
我们必须通过 T
存在来定义 T
。如果,宣布T
,
然后,您可以确定它是否已经已定义,您无需进入
任何困惑。
所以问题是确定T
是否是定义的类类型。
sizeof(T)
在这里没有帮助。如果T
未定义,那么它将给出一个
incomplete type T
错误。同样typeid(T)
。也没有任何好处
在类型T *
上制作SFINAE探针,因为T *
是定义的类型
只要T
已声明,即使T
不是。因为我们是
有义务宣布课程T
,std::is_class<T>
不是
或者回答,因为该声明足以说“是”。
C ++ 11在std::is_constructible<T ...Args>
中提供<type_traits>
。能够
这提供了一个现成的解决方案? - 如果定义了T
,那么它必须至少有一个构造函数。
我不敢。如果你知道至少一个公众的签名
T
的构造函数然后是GCC的<type_traits>
(截至4.6.3)确实会这样做
这生意。假设一个已知的公共构造函数是T::T(int)
。然后:
std::is_constructible<T,int>::value
如果定义T
,则为真,如果仅声明T
则为false。
但这不便携。 VC ++ 2010中的<type_traits>
尚未提供std::is_constructible
,如果std::has_trivial_constructor<T>
未定义,则其T
甚至会为barf:最有可能在std::is_constructible
到达时跟风。此外,在T
中只有std::is_constructible
的私人构造者可以提供给T
甚至GCC
将barf(眉毛抬起)。
如果定义了T
,它必须有一个析构函数,并且只有一个析构函数。并且该析构函数比T::~T
的任何其他可能成员更有可能公开。从这个角度来看,我们可以做的最简单和最强大的游戏是为T
的存在制作一个SFINAE探测器。
此SFINAE探针无法以常规方式确定
mf
是否具有普通成员函数&T::mf
- 使“是超载”
SFINAE探测函数的一个参数是用术语定义的
T
的类型。因为我们不允许带一个地址
析构函数(或构造函数)。
尽管如果定义了T::~T
,那么DT
的类型为decltype(dt)
- 必须是
每当dt
是一个计算结果为的表达式时,由T::~T
产生
调用DT *
;因此,#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
也将成为一种类型
原则作为函数重载的参数类型给出。所以我们
可以像这样编写探针(GCC 4.6.3):
T
只有decltype(std::declval<A>().~A())
必须在has_destructor<T>
的参数表达式中合法调用 public 析构函数的限制。 (std::declval<A>().~A()
是对我提供的方法 - 内省模板here的简化改编。)
该参数表达式std::declval<A>()
的含义可能对某些人来说是模糊的,特别是std::declval<T>()
。函数模板<type_traits>
在T&&
中定义并返回T
(对decltype
的rvalue-reference) - 尽管它只能在未评估的上下文中调用,例如参数std::declval<A>().~A()
。因此~A()
的含义是在给定A
时调用std::declval<A>()
。 T
通过避免需要A
的任何公共构建者或让我们了解它来为我们提供良好的服务。
因此,“Yes overload”的SFINAE探测器的参数类型是:指向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;
}
在提出问题之前你宣布它。这是一个测试程序。
// Defined
使用GCC 4.6.3构建,这将告诉您2 // Undefined
个类
有析构函数,2 int
类没有。第五
输出线会说std::has_trivial_destructor<int>
是可破坏的,最终的
该行将显示std::is_class<T>
同意。如果我们想要
要将字段缩小到类类型,可以在之后应用T
我们确定std::declval()
是可破坏的。
Visual C ++ 2010不提供has_destructor.h
。支持该编译器
您可以在#ifdef _MSC_VER
namespace std {
template <typename T>
typename add_rvalue_reference<T>::type declval();
}
#endif
的顶部添加以下内容:
{{1}}
答案 1 :(得分:11)
在这篇文章中仍未找到令人满意的答案......
迈克金汉开始正确的答案,并说了一个聪明的事情:
所以问题是确定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;
};
:
ID Date
------------------
20 03/07/2017
30 03/07/2017
30 17/07/2017
40 24/07/2017
50 24/07/2017
答案 2 :(得分: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;
<强> Demo. 强>
如果在全局命名空间中声明了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;
<强> Demo. 强>
答案 3 :(得分:0)
好吧,我想我找到了一种方法来做到这一点,尽管可能有更好的方法。假设我们有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.不幸的是,这仍然需要在两种情况下都要编译类的前向声明。我没有办法避免这种情况。