Edytuj: opublikowałem własną odpowiedź, zachowałem pierwotną zaakceptowaną odpowiedź... skłonił mnie do myślenia o aliasach.

Edytuj: Moje pytanie dotyczy możliwości odróżnienia niejednoznaczności od istnienia elementu członkowskiego var/func w kontekście SFINAE (lub innym). Moje pytanie nie dotyczy tego, jak stworzyć szablon has_member, ale wykrycia różnicy między niejednoznacznością a istnieniem

Czy można skonfigurować częściowe specjalizacje, które będą się rozróżniać, gdy dostęp do elementu członkowskiego jest niejednoznaczny (obie podstawy klasy pochodnej mają element członkowski) vs, czy element w ogóle istnieje (żadna podstawa klasy pochodnej nie ma elementu członkowskiego) ? Muszę zwrócić wartość true tylko, jeśli zostanie wykryta niejednoznaczność, ale nie, jeśli w ogóle nie ma elementu członkowskiego lub jeśli istnieje tylko dla jednej klasy. To jest to, co mam do tej pory, które zwraca true dla niejednoznaczności (czego chcę), false dla tylko jednej klasy posiadającej składową (również to, czego chcę), ale zwraca true jeśli żadna klasa nie ma składowej (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;
};

Pożądane użycie:

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;
}
3
Brett Rossier 9 czerwiec 2011, 01:11
3
Co rozumiesz przez „dostęp do członka jest niejednoznaczny”? Czy możesz podać przykład? Czy masz na myśli coś takiego jak f(int, int); f(double, double); f(1, 1.5);?
 – 
Kerrek SB
9 czerwiec 2011, 01:16
@Kerrek: Klasa WonderTwins używa wielokrotnego dziedziczenia, więc pobiera zarówno Zan, jak i hero_power członków Jayna. Ta klasa jest przekazywana do szablonu evil_is_vanquished, gdzie T::hero_power powoduje błąd, ponieważ nie określiłem, z której klasy bazowej 'hero_power' pochodzi. Ten błąd powoduje, że drugi szablon evil_is... jest ignorowany, co daje mi tylko pierwszy.
 – 
Brett Rossier
9 czerwiec 2011, 01:23
Dlaczego nie skorzystać z wirtualnego dziedziczenia? class Wondertwins : public virtual Zan, public virtual Jayna { ... }; Jeśli musisz odziedziczyć inny typ o tej samej nazwie, może napisz do niego mały interfejs? (Edycja: Hmm… może to jednak nie jest właściwa rzecz.) Mam na myśli, jaki byłby zamierzony cel WonderTwins::hero_power? Przykład użycia?
 – 
Kerrek SB
9 czerwiec 2011, 01:27
@Kerrek: nie jestem pewien, czy to rozwiązałoby problem z niejednoznacznością a istnieniem w szablonach evil_is_vanquished.
 – 
Brett Rossier
9 czerwiec 2011, 01:32
1
Nie zrozum mnie źle, może być tak, że pytanie ma jakąś abstrakcyjną, masochistyczną wartość, ale mogę się założyć, że coś jest nie tak z twoim projektem i że cokolwiek próbujesz zrobić, można zrobić bardziej elegancko. Dlaczego nie powiesz nam, co próbujesz zrobić? Aby odpowiedzieć na to pytanie, możesz spróbować połączyć cechy typu istnienia członka (sprawdź mój is_container, który sprawdza iteratory) z std::is_same na typach...
 – 
Kerrek SB
9 czerwiec 2011, 01:36

3 odpowiedzi

Najlepsza odpowiedź

W porządku, myślę, że udało mi się wziąć wyrażenia SFINAE i dodaj dedukcję typu. Oto bardzo prymitywnie zhakowana odpowiedź, która wydaje się robić coś pożytecznego (patrz przykład użycia na dole).

(Prezentacja mogłaby być prawdopodobnie o wiele bardziej zwięzła i czysta, ale w ten sposób można ją podzielić na etapy).

Użycie: conflicting_X<A, B>::value jest prawdziwe wtedy i tylko wtedy, gdy zarówno A, jak i B mają członka o nazwie x i typ tego członka jest różny (ściśle, nie zepsuty). Pytania pośrednie, takie jak to, czy członek jest zdefiniowany dla obu klas, można rozstrzygnąć za pomocą conflicting_X<A, B>::both i 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;
}
6
Community 23 maj 2017, 15:24
Wysłałem własną odpowiedź, nadal zachowuję twoją, ponieważ dało mi to do myślenia. Przyjrzałem się XType i compare_X i zastanawiałem się, czy byłoby możliwe wyizolowanie nazwy członka, zdefiniowanie dla niego aliasu, a następnie przekazanie tego aliasu do jednego has_member, który nie trzeba było kopiować/wklejać dla każdej nowej nazwy członka.
 – 
Brett Rossier
13 czerwiec 2011, 01:19

AKTUALIZACJA: ostatnio zrobiłem więcej z kodem, który opublikowałem w mojej oryginalnej odpowiedzi, więc aktualizuję to, aby uwzględnić zmiany/dodatki.

Oto kilka fragmentów dotyczących użycia: * Wnętrzności tego wszystkiego są dalej niżej

Sprawdź członka x w danej klasie. Może to być var, func, class, union lub enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

Sprawdź funkcję członka 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;

Sprawdź zmienną składową x:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

Sprawdź klasę członka x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Sprawdź związek członków x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Sprawdź listę członków x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Sprawdź jakąkolwiek funkcję członkowską x niezależnie od podpisu:

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;

LUB

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;

Szczegóły i rdzeń:

/*
    - 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;
};

Makra (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)
2
Brett Rossier 28 marzec 2012, 21:52

Nie sądzę, żeby to było możliwe. Aby SFINAE działał tutaj, potrzebujesz jednego lub więcej wyrażeń, które są w określonej prawidłowej/nieprawidłowej kombinacji tylko wtedy, gdy element, którego szukasz, jest niejednoznaczny. Zakłada to rozszerzone SFINAE dla C++0x.

Jednak biorąc pod uwagę typ T i potencjalny (nietypowy) element członkowski i, jakie są dostępne wyrażenia? &T::i i, jeśli ograniczysz sprawę tylko do członków danych, t.i, gdzie t ma typ T.

W przypadku obu wyrażeń, jeśli i jest niejednoznaczny, to każde z nich jest źle uformowane, ale jeśli jest obecne i nie jest niejednoznaczne, każde jest dobrze uformowane. Więc nie możemy spełnić Twoich wymagań.

Zauważ, że mając potencjalną bazę B z jednoznaczną składową danych i, możesz przetestować za pomocą &T::i i t.B::i. Jeśli pierwsze wyrażenie jest źle sformułowane, a drugie poprawnie, oznacza to, że istnieje niejednoznaczny element i. To najbardziej zbliżone do twoich wymagań.

1
Luc Danton 9 czerwiec 2011, 03:00
Cóż, zgadzam się na to, że nie jest to możliwe i prawdopodobnie mógłbym użyć kodu w takim stanie, w jakim jest. Wszystko działa tak, jak chcę, chyba że określę zmienną składową, której nie ma w ClassTwo (na przykład literówka lub w tym przypadku coś, co specjalnie przetestowałem). W tym momencie otrzymuję true wyrzucony, kiedy powinienem otrzymać false, i nie ma żadnej wskazówki, że żądany członek nigdzie nie istnieje:\
 – 
Brett Rossier
9 czerwiec 2011, 03:06
Poprawka... Dostaję true, gdy chcę false. Najwyraźniej zachowuje się tak, jak powinien i po prostu nie ma sposobu na rozróżnienie wszystkich trzech warunków w tym kontekście :(
 – 
Brett Rossier
9 czerwiec 2011, 03:12
Poczekaj, wyrażenie SFINAE jest bardzo interesujące... teraz wystarczy nam sposób na wyodrębnienie typu członka. Czy można to zrobić?
 – 
Kerrek SB
9 czerwiec 2011, 03:50
Z decltype tak. Napiszesz odpowiedź?
 – 
Luc Danton
9 czerwiec 2011, 03:52