为了创建算法模板函数,我需要知道类中的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;
}
但是在GNU C ++中进行编译时,它无法在Visual Studio中编译。使用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仅作为示例。可能有任何不明成员的课程。
P.S。请不要在这里发布C ++ 11解决方案,因为它们很明显且与问题无关。
答案 0 :(得分:79)
这是一个比 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
有一个名为static constexpr bool
的{{1}}成员,分别设置为value
和false
。因此,上面的两个模板true
继承了此成员。 (来自HasX
的第一个模板和来自std::false_type
的第二个模板。)
让我们开始简单,然后一步一步地进行,直到我们看到上面的代码。
1)起点:
std::true_type
在这种情况下,毫不奇怪:template <typename T, typename U>
struct HasX : std::false_type { };
派生自HasX
,因此std::false_type
和HasX<bool, double>::value == false
。
2)默认HasX<bool, int>::value == false
:
U
鉴于// Primary template
template <typename T, typename U = int>
struct HasX : std::false_type { };
默认为U
,int
实际上代表Has<bool>
,因此HasX<bool, int>
。
3)添加专业化:
HasX<bool>::value == HasX<bool, int>::value == false
通常,由于主要模板,// 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
的默认设置。
4)HasX<bool>::value == HasX<bool, int>::value == true
以及一种说出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)
。
班级类型怎么样?对于instace,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)
的奇特方式。
5)SFINAE:
这是一门完整的科学;-)好吧,我可以进行夸张,但这也不是很简单。因此,我会将解释保持在最低限度。
SFINAE代表替换失败不是错误。这意味着当一个模板参数被一个类型替换时,可能会出现一个非法的C ++代码,但在某些情况下,而不是中止编译,编译器只是忽略了有问题的代码,就好像它不是一样。在那里。让我们看看它如何适用于我们的案例:
int
在这里,// 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)
是一种奇特的方式,但有了SFINAE的好处。
当int
替换为类型时,可能会出现无效的构造。例如,T
不是有效的C ++,因此在bool::x
中用T
替换bool
会产生无效的构造。根据SFINAE原则,编译器不会拒绝代码,它只是忽略它的(部分)。更准确地说,正如我们所见,T::x
实际上意味着HasX<bool>
。应该选择HasX<bool, int>
的特化,但是在实例化时,编译器会找到U = int
并完全忽略模板特化,就好像它不存在一样。
此时,代码基本上与上面仅存在主模板的情况(2)相同。因此,bool::x
。
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
)中的任何地方都没有使用。然后我们可以取消命名第二个模板参数,并获得此帖子顶部显示的代码。
答案 1 :(得分:47)
另一种方式是这种方式,它也依赖于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的数据或函数成员,具有任意类型。引入成员名称的唯一目的是使成员名称查找可能存在歧义 - 成员的类型并不重要。
答案 2 :(得分:30)
我从一个已被关闭的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;
}
答案 3 :(得分:7)
更新:我最近使用我在原始答案中发布的代码完成了更多操作,因此我对此进行更新以考虑更改/添加内容。
以下是一些使用情况摘要: *所有这些的胆量更远
检查给定班级中的成员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;
OR
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;
};
宏(El Diablo!):
<强> 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)
答案 4 :(得分:4)
Boost.ConceptTraits在其他人之间提供了一些宏来定义类型特征,例如BOOST_TT_EXT_DEFINE_HAS_MEMBER(name)
,它定义了表单的类型特征:
has_member_##name<T>
如果T具有名为的成员类型,则为true。但请注意,这不会检测引用类型成员。
在你的情况下,添加一个头文件就足够了
BOOST_TT_EXT_DEFINE_HAS_MEMBER_TYPE(x)
并检查如下
BOOST_STATIC_ASSERT(has_member_x<P1>::value);
使用的技术与上述某些答案中解释的技术相同。
不幸的是这个库不再维护了。现在C ++ 0x将不包含概念,这个库与SFINAE一起是一个完美的替代品,可以与大多数概念一起使用。
答案 5 :(得分:2)
为什么不使用这样的专业化:
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; }
答案 6 :(得分:2)
第二个答案(litb's)显示了如何检测成员:
Is it possible to write a template to check for a function's existence?
答案 7 :(得分:1)
为什么不直接创建Check_x的模板特化?
template<> bool Check_x(P1 p) { return true; }
template<> bool Check_x(P2 p) { return false; }
哎呀,当我想起来的时候。如果您只有两种类型,为什么甚至需要模板?
答案 8 :(得分:1)
是抽象基类的函数(x,X,y,Y),还是可以重构它们?如果是这样,你可以使用Modern C ++ Design中的SUPERSUBCLASS()宏,以及来自这个问题的答案的想法:
答案 9 :(得分:1)
我们可以使用C++20 requires expression解决此问题。向最近在@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看到它。
答案 10 :(得分:0)
我们可以在编译时获得: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
对于class / struct:
struct t {
int aaa;
float bbb;
void func() {}
};