如何检测类中是不是存在特定的成员变量?
Posted
技术标签:
【中文标题】如何检测类中是不是存在特定的成员变量?【英文标题】:How to detect whether there is a specific member variable in class?如何检测类中是否存在特定的成员变量? 【发布时间】:2010-11-03 13:52:15 【问题描述】:为了创建算法模板函数,我需要知道作为模板参数的类中的 x 或 X(以及 y 或 Y)。在将我的函数用于 MFC CPoint 类或 GDI+ PointF 类或其他一些函数时,它可能很有用。他们都在其中使用不同的 x。我的解决方案可以简化为以下代码:
template<int> struct TT typedef int type;;
template<class P> bool Check_x(P p, typename TT<sizeof(&P::x)>::type b = 0) return true;
template<class P> bool Check_x(P p, typename TT<sizeof(&P::X)>::type b = 0) return false;
struct P1 int x; ;
struct P2 float X; ;
// it also could be struct P3 unknown_type X; ;
int main()
P1 p1 = 1;
P2 p2 = 1;
Check_x(p1); // must return true
Check_x(p2); // must return false
return 0;
但它不能在 Visual Studio 中编译,而在 GNU C++ 中编译。使用 Visual Studio,我可以使用以下模板:
template<class P> bool Check_x(P p, typename TT<&P::x==&P::x>::type b = 0) return true;
template<class P> bool Check_x(P p, typename TT<&P::X==&P::X>::type b = 0) return false;
但它不能在 GNU C++ 中编译。有没有通用的解决方案?
UPD:此处的结构 P1 和 P2 仅作为示例。可能存在任何具有未知成员的类。
附:请不要在此处发布 C++11 解决方案,因为它们很明显且与问题无关。
【问题讨论】:
我不相信第二种方式是标准的(整数常量表达式可能不使用 op== 与涉及 op& 的操作数)。但第一种方式看起来是对的。 msvc++ 对此有何评论? @litb:看看我答案末尾的链接 - 我认为这解释了问题(编译器拒绝它的原因,以及 C++98 标准是否真的允许) . +1:有趣的挑战 :-) 我已经为解决这个问题的正确代码写了详尽的解释,可以在这里找到:cpptalk.wordpress.com/2009/09/12/…。很抱歉两次发表此评论,但我觉得它属于主帖。 Is it possible to write a C++ template to check for a function's existence?的可能重复 【参考方案1】:这是一个比 Johannes Schaub - litb 的one 更简单的解决方案。它需要 C++11。
#include <type_traits>
template <typename T, typename = int>
struct HasX : std::false_type ;
template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type ;
更新:一个简单的例子以及它是如何工作的解释。
对于这些类型:
struct A int x; ;
struct B int y; ;
我们有HasX<A>::value == true
和HasX<B>::value == false
。让我们看看为什么。
首先回想std::false_type
和std::true_type
有一个名为value
的static constexpr bool
成员,该成员分别设置为false
和true
。因此,上面的两个模板HasX
继承了这个成员。 (第一个模板来自std::false_type
,第二个模板来自std::true_type
。)
让我们从简单开始,然后逐步进行,直到我们得到上面的代码。
1) 起点:
template <typename T, typename U>
struct HasX : std::false_type ;
在这种情况下,这并不奇怪:HasX
派生自 std::false_type
,因此是 HasX<bool, double>::value == false
和 HasX<bool, int>::value == false
。
2) 默认U
:
// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type ;
鉴于U
默认为int
,Has<bool>
实际上意味着HasX<bool, int>
,因此,HasX<bool>::value == HasX<bool, int>::value == false
。
3) 添加专业化:
// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type ;
// Specialization for U = int
template <typename T>
struct HasX<T, int> : std::true_type ;
一般来说,感谢主模板,HasX<T, U>
派生自 std::false_type
。但是,U = int
存在一个特化,它派生自 std::true_type
。因此,HasX<bool, double>::value == false
而是HasX<bool, int>::value == true
。
感谢U
、HasX<bool>::value == HasX<bool, int>::value == true
的默认设置。
4) decltype
和int
的一种花哨方式:
这里有点离题,但请多多包涵。
基本上(这并不完全正确),decltype(expression)
产生 表达式 的类型。例如,0
的类型为 int
,因此,decltype(0)
表示 int
。类似地,1.2
具有 double
类型,因此,decltype(1.2)
表示 double
。
考虑一个具有此声明的函数:
char func(foo, int);
foo
是一些类类型。如果f
是foo
类型的对象,那么decltype(func(f, 0))
表示char
(func(f, 0)
返回的类型)。
现在,表达式(1.2, 0)
使用(内置)逗号运算符按顺序计算两个子表达式(即首先是1.2
,然后是0
),丢弃第一个值并导致第二个。因此,
int x = (1.2, 0);
等价于
int x = 0;
把它和decltype
放在一起,decltype(1.2, 0)
就是int
。 1.2
或 double
在这里并没有什么特别之处。例如,true
的类型为 bool
,decltype(true, 0)
也表示 int
。
类类型呢?例如,decltype(f, 0)
是什么意思?很自然地期望这仍然意味着int
,但可能并非如此。实际上,逗号运算符可能存在重载,类似于上面的函数func
,它接受foo
和int
并返回char
。在这种情况下,decltype(foo, 0)
是 char
。
我们如何避免对逗号运算符使用重载?好吧,没有办法为void
操作数重载逗号运算符,我们可以将任何内容转换为void
。因此,decltype((void) f, 0)
表示int
。事实上,(void) f
将f
从foo
转换为void
,这基本上什么也没做,只是说必须将表达式视为具有void
类型。然后使用内置的运算符逗号,((void) f, 0)
的结果是 0
,其类型为 int
。因此,decltype((void) f, 0)
表示 int
。
这个演员真的有必要吗?好吧,如果逗号运算符采用foo
和int
没有过载,那么这不是必需的。我们总是可以检查源代码,看看是否有这样的操作符。但是,如果它出现在模板中并且f
的类型为V
,这是一个模板参数,那么就不再清楚(甚至不可能知道)逗号运算符的这种重载是否存在。为了通用,我们无论如何都会投射。
底线:decltype((void) f, 0)
是 int
的一种花哨方式。
5) SFINAE:
这是一门完整的科学 ;-) 好吧,我在夸大其词,但这也不是很简单。所以我会尽量减少解释。
SFINAE 代表替换失败不是错误。这意味着当模板参数被类型替换时,可能会出现非法 C++ 代码,但在某些情况下,编译器不会中止编译,而是简单地忽略有问题的代码,就好像它不存在一样.让我们看看它如何应用于我们的案例:
// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type ;
// Specialization for U = int
template <typename T>
struct HasX <T, decltype((void) T::x, 0)> : std::true_type ;
decltype((void) T::x, 0)
是 int
的一种别致的表达方式,但得益于 SFINAE。
当T
被替换为类型时,可能会出现无效的构造。例如,bool::x
不是有效的 C++,因此将T
替换为T::x
中的bool
会产生无效的构造。在 SFINAE 原则下,编译器不会拒绝代码,它只是忽略(部分)它。更准确地说,正如我们所见,HasX<bool>
实际上意味着HasX<bool, int>
。应该选择 U = int
的特化,但在实例化它时,编译器会找到 bool::x
并完全忽略模板特化,就好像它不存在一样。
此时,代码与上面仅存在主模板的情况 (2) 基本相同。因此,HasX<bool, int>::value == false
。
用于bool
的相同参数适用于B
,因为B::x
是无效构造(B
没有成员x
)。但是,A::x
是可以的,并且编译器在实例化 U = int
(或更准确地说,U = decltype((void) A::x, 0)
)的特化时没有发现任何问题。因此,HasX<A>::value == true
。
6) 取消命名U
:
好吧,再次查看 (5) 中的代码,我们看到名称 U
没有在任何地方使用,而是在其声明中使用 (typename U
)。然后我们可以取消第二个模板参数的名称,并获得本文顶部显示的代码。
【讨论】:
@Jurak Blaho:问题是 GCC 4.7.2。它适用于 Clang 3.2 和 IIRC,它也适用于 GCC 4.8。 (很遗憾liveworkspace 已经有一段时间没有工作了。) 请您再解释一下这是如何工作的,好吗? @DarioP:我添加了更新说明。正如你所看到的,解释很长,因为正确的细节数量总是取决于读者的知识。为了初学者的利益,我假设很少。我希望这很清楚,但不乏味。 谢谢,如果可以的话,我会第二次投票!这太棒了! 这是我见过的最干净、最简单的实现。对我来说,GCC 的问题是通过使用默认模板参数void
而不是int
,然后删除decltype
- ideone.com/QZHfai 中的逗号运算符来解决的。【参考方案2】:
另一种方式是这种方式,它也依赖于SFINAE for expressions。如果名称查找导致歧义,编译器将拒绝该模板
template<typename T> struct HasX
struct Fallback int x; ; // introduce member name "x"
struct Derived : T, Fallback ;
template<typename C, C> struct ChT;
template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1];
template<typename C> static char (&f(...))[2];
static bool const value = sizeof(f<Derived>(0)) == 2;
;
struct A int x; ;
struct B int X; ;
int main()
std::cout << HasX<A>::value << std::endl; // 1
std::cout << HasX<B>::value << std::endl; // 0
它基于 usenet 上某人的绝妙想法。
注意:HasX 检查任何名为 x 的数据或函数成员,具有任意类型。引入成员名称的唯一目的是为成员名称查找提供可能的歧义 - 成员的类型并不重要。
【讨论】:
一开始我并不理解这个想法。这正是我所需要的。此解决方案适用于 MSVC++ 2008 和 g++4.2.4。 是的,屏住呼吸:groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/…(从 SO 线程链接到你也由一个人链接)。如果您没有立即得到那个人的代码的作用,请不要担心。它非常聪明,我也花了很长时间 我在我的博客上添加了一篇关于这个的帖子,希望你们不要介意:) cpptalk.wordpress.com/2009/09/11/… 有趣的阅读。 @rmn,它确实不检查integer
成员,但它检查任意类型的名为 x
的任何数据或函数成员。引入成员名称的唯一目的是为成员名称查找提供可能的歧义 - 成员的 type 并不重要。
现在已发布此代码的完整说明.. at:cpptalk.wordpress.com/2009/09/12/…litb,非常欢迎您验证我是否正确:)【参考方案3】:
我从question 重定向到这里,该question 已作为此邮件的副本关闭。我知道这是一个旧线程,但我只是想建议一个可用于 C++11 的替代(更简单?)实现。假设我们要检查某个类是否有一个名为id
的成员变量:
#include <type_traits>
template<typename T, typename = void>
struct has_id : std::false_type ;
template<typename T>
struct has_id<T, decltype(std::declval<T>().id, void())> : std::true_type ;
就是这样。以下是它的使用方法 (live example):
#include <iostream>
using namespace std;
struct X int id; ;
struct Y int foo; ;
int main()
cout << boolalpha;
cout << has_id<X>::value << endl;
cout << has_id<Y>::value << endl;
使用几个宏可以让事情变得更简单:
#define DEFINE_MEMBER_CHECKER(member) \
template<typename T, typename V = bool> \
struct has_ ## member : false_type ; \
template<typename T> \
struct has_ ## member<T, \
typename enable_if< \
!is_same<decltype(declval<T>().member), void>::value, \
bool \
>::type \
> : true_type ;
#define HAS_MEMBER(C, member) \
has_ ## member<C>::value
可以这样使用:
using namespace std;
struct X int id; ;
struct Y int foo; ;
DEFINE_MEMBER_CHECKER(foo)
int main()
cout << boolalpha;
cout << HAS_MEMBER(X, foo) << endl;
cout << HAS_MEMBER(Y, foo) << endl;
【讨论】:
一个不错的解决方案,但如果 X 有一个私有成员变量 id,你会遇到问题。 我认为你根本不需要declval
,在我的测试中decltype(T::id, void())
工作正常【参考方案4】:
更新:我最近对我在原始答案中发布的代码做了更多处理,因此我正在更新它以考虑更改/添加。
下面是sn-ps的一些用法: *所有这一切的胆量都在更远的地方
检查给定类中的成员x
。可以是 var、func、class、union 或 enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
检查成员函数void x()
:
//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
检查成员变量x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
检查成员类x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
查看会员工会x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
检查成员枚举x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
无论签名如何,检查任何成员函数x
:
CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
或
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
细节和核心:
/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
template <typename... Args> struct ambiguate : public Args... ;
template<typename A, typename = void>
struct got_type : std::false_type ;
template<typename A>
struct got_type<A> : std::true_type
typedef A type;
;
template<typename T, T>
struct sig_check : std::true_type ;
template<typename Alias, typename AmbiguitySeed>
struct has_member
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
;
宏(暗黑破坏神!):
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> static const decltype(&T::member) value; ; \
\
struct AmbiguitySeed_##member char member; ; \
\
template<typename T> \
struct has_member_##member \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type ; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type ; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type ; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type ; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type ; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)
【讨论】:
可变参数模板在当前的 C++ 标准中无效。 也许重要的是要指出这不适用于继承的方法......但也许这是有意的。我猜 std::integral_constant 是这里的限制。【参考方案5】:Boost.ConceptTraits 提供了一些宏来定义类型特征,例如BOOST_TT_EXT_DEFINE_HAS_MEMBER(name)
,它定义了以下形式的类型特征:
has_member_##name<T>
如果 T 有一个名为 的成员类型,则为真。但是请注意,这不会检测到引用类型成员。
在你的情况下,添加一个头文件就足够了
BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x)
并检查如下
BOOST_STATIC_ASSERT(has_member_x<P1>::value);
使用的技术与前面一些答案中解释的技术相同。
不幸的是,这个库不再维护。现在 C++0x 将不包含概念,这个库与 SFINAE 一起是处理大多数概念的完美替代品。
【讨论】:
Concept Traits 不再维护,因为虽然该功能演变为这两个功能:boost.org/doc/libs/1_42_0/libs/mpl/doc/refmanual/… 其他功能进入了 Concepts_checks,例如:boost.org/doc/libs/1_42_0/libs/concept_check/… 你是对的。 has_xxx 已经在 Boost 中并回答问题。我不同意第二个链接,因为原型和概念检查是同一枚硬币的两个方面。【参考方案6】:你为什么不使用这样的专业化:
struct P1 int x; ;
struct P2 int X; ;
template<class P>
bool Check_x(P p) return true;
template<>
bool Check_x<P2>(P2 p) return false;
【讨论】:
因为我实际上并不知道 struct P2 包含大 X 而 P1 包含小 x。这些结构在这里只是举例。可以有任何结构或类。 然后我看不到任何使用模板重新识别它的方法(可能是我错了)。如果 x 的数据类型在 P1 和 P2 中不同,那么我们可以使用 sizeof 来返回 true 或 false。 在我的问题中有方法(实际上有两种不同的方法)。但我不知道如何在两个编译器中识别它。【参考方案7】:我们可以使用C++20 requires expression 来解决这个问题。 h/t 到@lefticus 最近在C++ Weekly - Ep 242 - Design By Introspection in C++20 (concepts + if constexpr 上发布了这个方法:
#include <iostream>
struct P1 int x;;
struct P2 float X;;
bool has_x(const auto &obj)
if constexpr (requires obj.x;)
return true;
else
return false;
int main()
P1 p1 = 1;
P2 p2 = 1;
std::cout << std::boolalpha << has_x(p1) << "\n";
std::cout << has_x(p2) << "\n";
return 0;
你可以看到live here。
【讨论】:
【参考方案8】:函数 (x, X, y, Y) 是来自抽象基类,还是可以重构为这样?如果是这样,您可以使用 Modern C++ Design 中的 SUPERSUBCLASS() 宏,以及这个问题的答案中的想法:
Compile-time type based dispatch
【讨论】:
【参考方案9】:为什么不直接创建 Check_x 的模板特化?
template<> bool Check_x(P1 p) return true;
template<> bool Check_x(P2 p) return false;
哎呀,当我想到它的时候。如果你只有两种类型,那你为什么还需要模板呢?
【讨论】:
不止两种类型。看看我对 Naveen 回答的评论。【参考方案10】:我们可以在编译时获得:0 - not_member, 1 - is_object, 2 - is_function
为每个所需的类和成员 - 对象或函数:http://ideone.com/Fjm9u5
#include <iostream>
#include <type_traits>
#define IS_MEMBER(T1, M) \
struct \
struct verystrangename1 bool M; ; \
template<typename T> struct verystrangename2 : verystrangename1, public T ; \
\
enum return_t not_member, is_object, is_function ; \
template<typename T, typename = decltype(verystrangename2<T>::M)> constexpr return_t what_member() return not_member; \
template<typename T> typename std::enable_if<std::is_member_object_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() return is_object; \
template<typename T> typename std::enable_if<std::is_member_function_pointer<decltype(&T::M)>::value, return_t>::type constexpr what_member() return is_function; \
constexpr operator return_t() return what_member<T1>(); \
struct t
int aaa;
float bbb;
void func()
;
// Can't be in function
IS_MEMBER(t, aaa) is_aaa_member_of_t;
IS_MEMBER(t, ccc) is_ccc_member_of_t;
IS_MEMBER(t, func) is_func_member_of_t;
// known at compile time
enum const_is_aaa_member_of_t = (int)is_aaa_member_of_t ;
static constexpr int const_is_func_member_of_t = is_func_member_of_t;
int main()
std::cout << std::boolalpha << "0 - not_member, 1 - is_object, 2 - is_function \n\n" <<
"is aaa member of t = " << is_aaa_member_of_t << std::endl <<
"is ccc member of t = " << is_ccc_member_of_t << std::endl <<
"is func member of t = " << is_func_member_of_t << std::endl <<
std::endl;
return 0;
结果:
0 - not_member, 1 - is_object, 2 - is_function
is aaa member of t = 1
is ccc member of t = 0
is func member of t = 2
对于类/结构:
struct t
int aaa;
float bbb;
void func()
;
【讨论】:
以上是关于如何检测类中是不是存在特定的成员变量?的主要内容,如果未能解决你的问题,请参考以下文章
继承的基本概念: Java不支持多继承,也就是说子类至多只能有一个父类。 子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法。 子类中定义的成员变量和父类中