Edit: Posted an answer of my own, kept the original accepted answer... got me thinking about aliases.
Edit: My question is directed at the possibility of differentiating ambiguity vs existence of a member var/func in SFINAE (or other) contexts. My question isn't about how to make a has_member template, it's about detecting a difference between ambiguity vs existence
Is it possible to set up partial specializations that will differentiate when a member is being accessed in an ambiguous way (both bases of a derived class have the member) vs if the member exists at all (neither base of a derived class have the member)? I need to return true only if ambiguity is detected, but not if there's no member at all,开发者_如何学编程 or if it exists only for one class. This is what I have so far, which returns true for ambiguity (what I want), false for only one class having the member (also what I want), but returns true if neither class has the member (argh!)
//for the sake of this example, ClassOne comes from a lib I don't control
struct ClassOne {
//some unknown members in here...
};
struct ClassTwo {
string member_var;
};
template<typename A>
struct helper : std::true_type {};
template<typename A, typename B>
struct merged_class : public A, public B {};
template<typename T, typename = void>
struct has_member_var : std::true_type {
//Member request ambiguous or neither class has member.
//I want to catch these conditions separately,
//but this one template catches both :(
const int status = 1;
};
template<typename T>
struct has_member_var<
T
, typename std::enable_if<
//The next line results in either ambiguous member request error
//if both classes have the member OR
//member does not exist error if neither class has the member
//...how to tell the difference in the type of error?
helper<decltype(T::member_var)>::value
, T
>::type
> : std::false_type {
const int status = 2; //only one class has member
};
//This next one I have no idea how to do, if it's even possible.
//I'd like a third specialization that will match either the
//ambiguous condition or the member only existing in one of the
//base classes.
template<typename T>
struct has_member<
T
, typename std::enable_if<
some_freaky_magic<decltype(T::foo)>::true_value
, T
>::type
> : std::true_type {
const int status = 3;
};
Desired Usage:
switch(has_member<merged_class<ClassOne, ClassTwo>>::status) {
case 1:
cout << "member ambiguity";
break;
case 2:
cout << "member non-existence";
break;
case 3:
cout << "member existence for only one base";
break;
}
Alright, I think I managed to take the expression SFINAE approach and add type deduction. Here is a very crudely hacked-together answer that appears to be doing something useful (see the usage example at the bottom).
(The presentation could probably made a lot more succinct and clean, but this way you see it broken down into steps.)
Usage: conflicting_X<A, B>::value
is true if and only if both A
and B
have a member called x
and the type of this member differs (strictly, not decayed). Intermediate questions such as whether the member is defined for both classes can be decided with conflicting_X<A, B>::both
and has_X<T>::value
.
#include <iostream>
#include <type_traits>
#include <typeinfo>
// has_X is taken straight from the other topic
template <typename T>
struct has_X
{
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;
};
// Here we go...
template <typename T>
struct XType
{
typedef decltype(T::x) type;
};
template <bool, typename S, typename T>
struct compare_X
{
static const bool value = false;
};
template <typename S, typename T>
struct compare_X<true, S, T>
{
// Note that we don't decay, we want equality on the nose.
static const bool value = ! std::is_same<typename XType<S>::type, typename XType<T>::type>::value;
};
template <typename S, typename T>
struct conflicting_X
{
// We split this up so that XType is only instantiated if T::x really exists.
// You may also use conflicting_X::both as a useful datum.
static const bool both = has_X<S>::value && has_X<T>::value;
static const bool value = compare_X<both, S, T>::value;
};
/*** Example ***/
struct A { int x; };
struct B { int X; };
struct C { double x; };
void f(double) { }
int main() {
std::cout << has_X<A>::value << std::endl; // 1
std::cout << has_X<B>::value << std::endl; // 0
std::cout << "Conflict A/B? " << conflicting_X<A, B>::value << std::endl;
std::cout << "Conflict A/C? " << conflicting_X<A, C>::value << std::endl;
}
UPDATE: I've recently done some more with the code I posted in my original answer, so I'm updating this to account for changes/additions.
Here are some usage snippets: *The guts for all this are farther down
Check for member x
in a given class. Could be var, func, class, union, or enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
Check for member function 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;
Check for member variable x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
Check for member class x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
Check for member union x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
Check for member enum x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
Check for any member function x
regardless of signature:
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;
Details and core:
/*
- 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;
};
Macros (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)
I don't think this is possible. For SFINAE to work here, you need one or more expressions that are in a particular valid/ill-formed combination only when the member you're looking for is ambiguous. This is assuming extended SFINAE for C++0x.
However, given type T
and a potential (non-type) member i
, what are the expressions available? &T::i
and, if you limit your case to data members only, t.i
where t
has type T
.
For both expressions, if i
is ambiguous then each is ill-formed, but if it's present and not ambiguous each is well formed. So we can't satisfy your requirements.
Note that given a potential base B
with unambiguous data member i
, you can test with &T::i
and t.B::i
. If the first expression is ill-formed and the second is well-formed, this means that there is an ambiguous member i
. That's the closest I can come to your requirements.
精彩评论