3

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;
}
Brett Rossier
  • 3,420
  • 3
  • 27
  • 36
  • 3
    What do you mean by "a member is accessed in an ambiguous way"? Could you give an example? Do you mean something like `f(int, int); f(double, double); f(1, 1.5);`? – Kerrek SB Jun 08 '11 at 21:16
  • @Kerrek: The `WonderTwins` class uses multiple inheritance, so it gets both `Zan`'s and `Jayna`'s `hero_power` members. That class gets passed into the `evil_is_vanquished` template, where `T::hero_power` results in an error because I didn't specify which base class 'hero_power' comes from. That error causes the second `evil_is...` template to be ignored, giving me just the first one. – Brett Rossier Jun 08 '11 at 21:23
  • Why not use virtual inheritance? `class Wondertwins : public virtual Zan, public virtual Jayna { ... };` If you must inherit a different type by the same name, maybe write a little interface for it? (_Edit:_ Hmm.. maybe that isn't the right thing though.) I mean, what would the intended purpose for `WonderTwins::hero_power` be? Usage example? – Kerrek SB Jun 08 '11 at 21:27
  • @Kerrek: not sure that would resolve the issue with ambiguity vs existence in the `evil_is_vanquished` templates. – Brett Rossier Jun 08 '11 at 21:32
  • @Kerrek I corrected my usage example (bottom of question). It was wrong before. Oops :\ – Brett Rossier Jun 08 '11 at 21:36
  • 1
    Don't get me wrong, it could be that the question has some abstract, masochistic merit, but I'm willing to bet that something is off with your design and that whatever you're trying to do can be done more elegantly. Why not tell us what you're trying to do? To answer the question, you could try combining member existence typetraits (check out my [is_container](http://stackoverflow.com/questions/4850473/pretty-print-c-stl-containers), which checks for iterators) with a `std::is_same` on the types... – Kerrek SB Jun 08 '11 at 21:36
  • Well, yes I'm trying to test for the existence of a member, and I've seen solutions to that effect, but my question really is about if it's possible to differentiate between "this member does not exist at all" and "the way you're accessing this member is ambiguous" in an SFINAE (or other) context. – Brett Rossier Jun 08 '11 at 21:40
  • What are those "solutions to that effect"? I'm beginning to think that it's not possible to do that. – Kerrek SB Jun 08 '11 at 22:08
  • http://stackoverflow.com/questions/1005476/how-to-detect-whether-there-is-a-specific-member-variable-in-class is just one. I've seen several ways people have done it, not all of which use SFINAE. – Brett Rossier Jun 08 '11 at 22:58

3 Answers3

6

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;
}
Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Posted an answer of my own, still keeping yours since it got me thinking. I got to looking at `XType` and `compare_X`, and wondered if it would be possible to isolate the member name, define an alias for it, and then pass that alias into a single `has_member` that didn't have to be copy/pasted for each new member name. – Brett Rossier Jun 12 '11 at 21:19
2

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)
Brett Rossier
  • 3,420
  • 3
  • 27
  • 36
1

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.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • Well, I'm willing to accept that it's not possible, and I could probably use the code as it is. Everything works like I want, unless I specify a member variable that isn't in `ClassTwo` (for instance a typo, or in this case, something I specifically tested for). At that point I get a `true` kicked back when I should get a `false`, and no indication at all that the requested member doesn't exist anywhere :\ – Brett Rossier Jun 08 '11 at 23:06
  • Correction... I get a `true` kicked back when I *want* a `false`. Apparently it's behaving as it should and there's just no way to make a distinction amongst all three conditions in that context :( – Brett Rossier Jun 08 '11 at 23:12
  • Wait, the [expression SFINAE](http://stackoverflow.com/questions/1005476/how-to-detect-whether-there-is-a-specific-member-variable-in-class) is very interesting ... now we just need a way to extract the type of the member. Can that be done? – Kerrek SB Jun 08 '11 at 23:50
  • @Kerrek With `decltype`, yes. Will you be writing an answer? – Luc Danton Jun 08 '11 at 23:52