Sprawdź, czy klasa ma funkcję składową o danym sygnaturze

139

Proszę o sztuczkę szablonową, aby wykryć, czy klasa ma określoną funkcję członkowską danego podpisu.

Problem jest podobny do tego cytowanego tutaj http://www.gotw.ca/gotw/071.htm, ale nie ten sam: w pozycji książki Suttera odpowiedział na pytanie, że klasa C MUSI ZAPEWNIĆ funkcję składową z konkretny podpis, w przeciwnym razie program się nie skompiluje. W moim problemie muszę coś zrobić, jeśli klasa ma taką funkcję, inaczej zrobić „coś innego”.

Podobny problem napotkał boost :: serialization, ale nie podoba mi się przyjęte przez nich rozwiązanie: funkcja szablonu, która domyślnie wywołuje funkcję swobodną (którą musisz zdefiniować) z określonym podpisem, chyba że zdefiniujesz określoną funkcję składową ( w ich przypadku "serialize", który przyjmuje 2 parametry danego typu) z określonym podpisem, w przeciwnym razie wystąpi błąd kompilacji. Oznacza to implementację serializacji natrętnej i nieinwazyjnej.

Nie podoba mi się to rozwiązanie z dwóch powodów:

  1. Aby nie przeszkadzać, musisz przesłonić globalną funkcję „serialize”, która znajduje się w przestrzeni nazw boost :: serialization, więc masz KOD KLIENTA do otwierania przyspieszenia przestrzeni nazw i serializacji przestrzeni nazw!
  2. Stos do rozwiązania tego bałaganu wynosił od 10 do 12 wywołań funkcji.

Muszę zdefiniować niestandardowe zachowanie dla klas, które nie mają tej funkcji członkowskiej, a moje jednostki znajdują się w różnych przestrzeniach nazw (i nie chcę zastępować funkcji globalnej zdefiniowanej w jednej przestrzeni nazw, gdy jestem w innej)

Czy możesz mi podpowiedzieć, jak rozwiązać tę zagadkę?

ugasoft
źródło
1
Podobne pytanie: stackoverflow.com/questions/257288
Johannes Schaub - litb
@ R.MartinhoFernandes Jakiej odpowiedzi szukasz? Ta odpowiedź Mike'a Kinghana jest dość głęboka i wykorzystuje elementy C ++ 11.
jrok
@ R.MartinhoFernandes Może to nowoczesna wersja, której szukasz?
Daniel Frey

Odpowiedzi:

92

Nie jestem pewien, czy dobrze cię rozumiem, ale możesz wykorzystać SFINAE do wykrywania obecności funkcji w czasie kompilacji. Przykład z mojego kodu (sprawdza, czy klasa ma funkcję składową size_t used_memory () const).

template<typename T>
struct HasUsedMemoryMethod
{
    template<typename U, size_t (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
    template<typename U> static int Test(...);
    static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
        // We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
    ReportMemUsage(m, 
        std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
yrp
źródło
15
co to do cholery jest??? czy jest to legalny kod C ++? czy możesz napisać "template <nazwa typu U, size_t (U :: *) () const>" ?? ale ... to świetne i nowe rozwiązanie! Dziękuję, jutro przeanalizuję lepiej z moimi kolegami ... świetnie!
ugasoft
2
W przykładzie brakuje definicji „int_to_type”. Oczywiście to nie dodaje odpowiedzi, ale oznacza, że ​​ludzie mogą zobaczyć Twój kod w akcji po szybkim wycinaniu i wklejaniu.
Richard Corden,
2
Prosta definicja int_to_type mogłaby wyglądać następująco: 'template <int N> struct int_to_type {};'. Wiele implementacji zachowuje wartość parametru N albo w wyliczeniu, albo w statycznej stałej całkowitej (template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
David Rodríguez - dribeas
2
Po prostu weź boost :: integral_constant zamiast int_to_type.
Vadim Ferderer
2
@JohanLundberg Jest to wskaźnik do (niestatycznej) funkcji składowej. Na przykład size_t(std::vector::*p)() = &std::vector::size;.
Unslander Monica
139

Oto możliwa implementacja oparta na funkcjach C ++ 11. Prawidłowo wykrywa funkcję, nawet jeśli jest dziedziczona (w przeciwieństwie do rozwiązania w zaakceptowanej odpowiedzi, jak zauważa Mike Kinghan w swojej odpowiedzi ).

Funkcja, dla której testowany jest ten fragment kodu, nazywa się serialize:

#include <type_traits>

// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.

template<typename, typename T>
struct has_serialize {
    static_assert(
        std::integral_constant<T, false>::value,
        "Second template parameter needs to be of function type.");
};

// specialization that does the checking

template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
    template<typename T>
    static constexpr auto check(T*)
    -> typename
        std::is_same<
            decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
            Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        >::type;  // attempt to call it and see if the return type is correct

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(0)) type;

public:
    static constexpr bool value = type::value;
};

Stosowanie:

struct X {
     int serialize(const std::string&) { return 42; } 
};

struct Y : X {};

std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
jrok
źródło
Czy to działa, jeśli Y nie ma metody o nazwie „serialize”? Nie widzę, jak zwróciłoby wartość false, gdyby metoda „serialize” nie istniała.
Collin,
1
@Collin w takim przypadku podstawienie parametru szablonu nie powiedzie się dla pierwszego przeciążenia czeku i zostanie odrzucone z zestawu przeciążeń. Wraca do drugiej, która zwraca false_type. To nie jest błąd kompilatora, ponieważ zasada SFINAE.
jrok
1
@ elios264 Nie ma. Możesz użyć makra, aby napisać szablon dla każdej funkcji, którą chcesz sprawdzić.
jrok
1
Czy jest jakiś szczególny powód, dla którego argument do sprawdzenia jest typu T * zamiast T lub T &?
shibumi
1
Ale co, jeśli serializesam zaakceptuje szablon. Czy istnieje sposób na sprawdzenie serializeistnienia bez wpisywania dokładnego typu?
Cześć Angel,
37

Przyjęta odpowiedź na to pytanie o introspekcję funkcji składowych w czasie kompilacji, chociaż jest słusznie popularna, ma pewien problem, który można zaobserwować w następującym programie:

#include <type_traits>
#include <iostream>
#include <memory>

/*  Here we apply the accepted answer's technique to probe for the
    the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
    template<typename U, E (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::operator*>*);
    template<typename U> static int Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

using namespace std;

/* Here we test the `std::` smart pointer templates, including the
    deprecated `auto_ptr<T>`, to determine in each case whether
    T = (the template instantiated for `int`) provides 
    `int & T::operator*() const` - which all of them in fact do.
*/ 
int main(void)
{
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
    return 0;
}

Zbudowany z GCC 4.6.3, wyjść programowych 110- informując nas, że T = std::shared_ptr<int>ma nie zapewniająint & T::operator*() const .

Jeśli nie jesteś jeszcze mądry w tej kwestii, to spojrzenie na definicję std::shared_ptr<T>w nagłówku <memory>rzuci światło. W tej implementacji std::shared_ptr<T>pochodzi z klasy bazowej, z której dziedziczy operator*() const. Zatem instancja szablonu, SFINAE<U, &U::operator*>która polega na „znalezieniu” operatora dla U = std::shared_ptr<T>, nie nastąpi, ponieważ std::shared_ptr<T>ma operator*()własne prawo, a instancja szablonu nie „dziedziczy”.

Ten szkopuł nie wpływa na dobrze znane podejście SFINAE, wykorzystujące "Sztukę sizeof ()", do wykrywania jedynie, czy Tma jakąś funkcję składową mf(patrz np. Ta odpowiedź i komentarze). Ale ustalenie, że T::mfistnieje, często (zwykle?) Nie jest wystarczające: może być również konieczne ustalenie, czy ma on pożądany podpis. To jest miejsce, w którym zilustrowana technika zdobywa punkty. Wskazany wariant żądanego podpisu jest wpisany w parametr typu szablonu, który musi zostać spełniony, &T::mfaby sonda SFINAE zakończyła się powodzeniem. Ale ta technika tworzenia wystąpienia szablonu daje błędną odpowiedź, gdy T::mfjest dziedziczona.

Bezpieczna technika SFINAE do introspekcji w czasie kompilacji T::mfmusi unikać stosowania&T::mf argumentu szablonu w celu utworzenia instancji typu, od którego zależy rozdzielczość szablonu funkcji SFINAE. Zamiast tego rozdzielczość funkcji szablonu SFINAE może zależeć tylko od dokładnie odpowiednich deklaracji typu używanych jako typy argumentów przeciążonej funkcji sondy SFINAE.

Odpowiadając na pytanie, które podlega temu ograniczeniu, zilustruję dla wykrywania w czasie kompilacji E T::operator*() const, arbitralnych Ti E. Ten sam wzorzec będzie stosowany mutatis mutandis do sondowania dla dowolnej innej sygnatury metody składowej.

#include <type_traits>

/*! The template `has_const_reference_op<T,E>` exports a
    boolean constant `value that is true iff `T` provides
    `E T::operator*() const`
*/ 
template< typename T, typename E>
struct has_const_reference_op
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A>
    static std::true_type test(E (A::*)() const) {
        return std::true_type();
    }

    /* SFINAE operator-exists :) */
    template <typename A> 
    static decltype(test(&A::operator*)) 
    test(decltype(&A::operator*),void *) {
        /* Operator exists. What about sig? */
        typedef decltype(test(&A::operator*)) return_type; 
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type(); 
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0,0)) type;

    static const bool value = type::value; /* Which is it? */
};

W tym rozwiązaniu przeciążona funkcja sondy SFINAE test() jest „wywoływana rekurencyjnie”. (Oczywiście w rzeczywistości nie jest w ogóle wywoływana; ma jedynie zwracane typy hipotetycznych wywołań rozwiązanych przez kompilator).

Musimy zbadać co najmniej jeden, a co najwyżej dwa punkty informacji:

  • Czy T::operator*()w ogóle istnieje? Jeśli nie, to koniec.
  • Biorąc pod uwagę, że T::operator*()istnieje, czy jego podpis E T::operator*() const?

Odpowiedzi uzyskujemy, oceniając typ zwracania pojedynczego wywołania test(0,0). Robi się to przez:

    typedef decltype(test<T>(0,0)) type;

To wywołanie może zostać rozwiązane jako /* SFINAE operator-exists :) */przeciążenie test()lub może zostać rozwiązane jako /* SFINAE game over :( */przeciążenie. Nie może rozwiązać problemu z /* SFINAE operator-has-correct-sig :) */przeciążeniem, ponieważ ten oczekuje tylko jednego argumentu, a my przekazujemy dwa.

Dlaczego mijamy dwóch? Wystarczy wymusić wyłączenie w rezolucji /* SFINAE operator-has-correct-sig :) */ . Drugi argument nie ma innego znaczenia.

To wywołanie test(0,0)zostanie rozwiązane na /* SFINAE operator-exists :) */wypadek, gdyby pierwszy argument 0 spełniał pierwszy typ parametru tego przeciążenia, czyli decltype(&A::operator*)with A = T. 0 spełni ten typ na wszelki wypadek T::operator*.

Załóżmy, że kompilator powie na to tak. Potem idzie dalej /* SFINAE operator-exists :) */i musi określić typ zwracania wywołania funkcji, którym w tym przypadku jest decltype(test(&A::operator*))- typ zwracania jeszcze jednego wywołania funkcji test().

Tym razem mijamy tylko jeden argument, o &A::operator*którym teraz wiemy, że istnieje lub nie byłoby nas tutaj. Wezwanie do test(&A::operator*)może rozwiązać się /* SFINAE operator-has-correct-sig :) */albo ponownie, albo ponownie, może rozwiązać się /* SFINAE game over :( */. Wywołanie będzie pasować /* SFINAE operator-has-correct-sig :) */tylko w przypadku, gdy &A::operator*spełnia pojedynczy typ parametru tego przeciążenia, czyli E (A::*)() constwith A = T.

Kompilator powie tutaj tak, jeśli T::operator*ma żądany podpis, a następnie ponownie musi ocenić zwracany typ przeciążenia. Koniec z „rekurencjami” teraz: tak std::true_type.

Jeśli kompilator nie wybierze /* SFINAE operator-exists :) */dla wywołania test(0,0)lub nie wybierze /* SFINAE operator-has-correct-sig :) */ dla wywołania test(&A::operator*), to w każdym przypadku idzie z, /* SFINAE game over :( */a ostatecznym typem zwracanym jest std::false_type.

Oto program testowy, który pokazuje szablon generujący oczekiwane odpowiedzi w różnych próbkach przypadków (ponownie GCC 4.6.3).

// To test
struct empty{};

// To test 
struct int_ref
{
    int & operator*() const {
        return *_pint;
    }
    int & foo() const {
        return *_pint;
    }
    int * _pint;
};

// To test 
struct sub_int_ref : int_ref{};

// To test 
template<typename E>
struct ee_ref
{
    E & operator*() {
        return *_pe;
    }
    E & foo() const {
        return *_pe;
    }
    E * _pe;
};

// To test 
struct sub_ee_ref : ee_ref<char>{};

using namespace std;

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    cout << "Expect Yes" << endl;
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value;
    cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
    cout << has_const_reference_op<std::vector<int>::const_iterator,
            int const &>::value;
    cout << has_const_reference_op<int_ref,int &>::value;
    cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
    cout << "Expect No" << endl;
    cout << has_const_reference_op<int *,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,char &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int>::value;
    cout << has_const_reference_op<unique_ptr<long>,int &>::value;
    cout << has_const_reference_op<int,int>::value;
    cout << has_const_reference_op<std::vector<int>,int &>::value;
    cout << has_const_reference_op<ee_ref<int>,int &>::value;
    cout << has_const_reference_op<sub_ee_ref,int &>::value;
    cout << has_const_reference_op<empty,int &>::value  << endl;
    return 0;
}

Czy są jakieś nowe błędy w tym pomyśle? Czy można uczynić go bardziej ogólnym bez ponownego wpadania w zaczep, którego unika?

Mike Kinghan
źródło
18

Oto kilka fragmentów użycia: * Wnętrzności tego wszystkiego znajdują się dalej

Sprawdź, czy jest członek xdanej klasy. 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łonkowską x:

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

Sprawdź związek członkowski x:

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

Sprawdź wyliczenie członków x :

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

Sprawdź, czy istnieje funkcja członkowska 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.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

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)
Brett Rossier
źródło
1
To jest świetne; byłoby miło umieścić to w jednej bibliotece plików nagłówkowych.
Allan
12

Powinno to wystarczyć, jeśli znasz nazwę oczekiwanej funkcji składowej. (W tym przypadku wystąpienie funkcji bla nie powiedzie się, jeśli nie ma funkcji składowej (napisanie takiej, która i tak działa, jest trudne, ponieważ brakuje częściowej specjalizacji funkcji. Może być konieczne użycie szablonów klas). Ponadto struktura enable (która jest podobny do enable_if) może również zostać utworzony na podstawie typu funkcji, którą ma mieć jako element członkowski.

template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
  A a;
  B b;
  bla(b);
  bla(a);
}
coppro
źródło
4
thaks! jest podobny do rozwiązania proponowanego przez yrp. Nie wiedziałem, że szablon może być oparty na funkcjach składowych. To nowa funkcja, której się dzisiaj nauczyłem! ... i nowa lekcja: „nigdy nie mów, że jesteś ekspertem w c ++” :)
ugasoft
7

Oto prostsze podejście do odpowiedzi Mike'a Kinghana. To wykryje metody dziedziczone. Sprawdza również dokładny podpis (w przeciwieństwie do podejścia jroka, które umożliwia konwersję argumentów).

template <class C>
class HasGreetMethod
{
    template <class T>
    static std::true_type testSignature(void (T::*)(const char*) const);

    template <class T>
    static decltype(testSignature(&T::greet)) test(std::nullptr_t);

    template <class T>
    static std::false_type test(...);

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");

Wykonalny przykład

Valentin Milea
źródło
To dobrze, ale nie zadziała, jeśli funkcja nie przyjmuje argumentów
Triskeldeian
Działa świetnie. Nie miałem żadnych problemów ze stosowaniem tej sztuczki do funkcji składowych nie pobierających żadnych argumentów.
JohnB
Działa to dobrze dla mnie z wieloma argumentami metody i bez argumentów metod, w tym z przeciążeniami i włączaniem z dziedziczeniem oraz z użyciem usingdo przenoszenia przeciążeń z klasy bazowej. Działa dla mnie na MSVC 2015 i Clang-CL. Jednak nie działa z MSVC 2012.
steveire
5

Możesz użyć std :: is_member_function_pointer

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
Yochai Timmer
źródło
18
Nie &A::foobyć błąd kompilacji, jeśli nie ma foow ogóle A? Przeczytałem pierwotne pytanie jako mające działać z dowolną klasą wejściową, a nie tylko z tymi, które mają nazwanego członka foo.
Jeff Walden,
To nie działa. Daje błąd kompilacji, jeśli funkcja nie jest członkiem A.
Marc Dirven
5

Sam miałem ten sam problem i stwierdziłem, że proponowane tutaj rozwiązania są bardzo interesujące ... ale wymagałem rozwiązania, które:

  1. Wykrywa również funkcje dziedziczone;
  2. Jest kompatybilny z kompilatorami nie gotowymi na C ++ 11 (więc bez decltype)

Znalazłem inny wątek proponujący coś takiego na podstawie dyskusji BOOST . Oto uogólnienie proponowanego rozwiązania jako deklaracji dwóch makr dla klasy cech, zgodnie z modelem klas boost :: has_ ​​* .

#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>

/// Has constant function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)

/// Has non-const function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)

// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...)  \
    template                                                                  \
    <   typename Type,                                                        \
        bool is_class = boost::is_class<Type>::value                          \
    >                                                                         \
    class has_func_ ## func_name;                                             \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,false>                                  \
    {public:                                                                  \
        BOOST_STATIC_CONSTANT( bool, value = false );                         \
        typedef boost::false_type type;                                       \
    };                                                                        \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,true>                                   \
    {   struct yes { char _foo; };                                            \
        struct no { yes _foo[2]; };                                           \
        struct Fallback                                                       \
        {   func_ret_type func_name( __VA_ARGS__ )                            \
                UTILITY_OPTIONAL(func_const,const) {}                         \
        };                                                                    \
        struct Derived : public Type, public Fallback {};                     \
        template <typename T, T t>  class Helper{};                           \
        template <typename U>                                                 \
        static no deduce(U*, Helper                                           \
            <   func_ret_type (Fallback::*)( __VA_ARGS__ )                    \
                    UTILITY_OPTIONAL(func_const,const),                       \
                &U::func_name                                                 \
            >* = 0                                                            \
        );                                                                    \
        static yes deduce(...);                                               \
    public:                                                                   \
        BOOST_STATIC_CONSTANT(                                                \
            bool,                                                             \
            value = sizeof(yes)                                               \
                == sizeof( deduce( static_cast<Derived*>(0) ) )               \
        );                                                                    \
        typedef ::boost::integral_constant<bool,value> type;                  \
        BOOST_STATIC_CONSTANT(bool, is_const = func_const);                   \
        typedef func_ret_type return_type;                                    \
        typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;                \
    }

// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__

Te makra rozwijają się do klasy cech z następującym prototypem:

template<class T>
class has_func_[func_name]
{
public:
    /// Function definition result value
    /** Tells if the tested function is defined for type T or not.
    */
    static const bool value = true | false;

    /// Function definition result type
    /** Type representing the value attribute usable in
        http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
    */
    typedef boost::integral_constant<bool,value> type;

    /// Tested function constness indicator
    /** Indicates if the tested function is const or not.
        This value is not deduced, it is forced depending
        on the user call to one of the traits generators.
    */
    static const bool is_const = true | false;

    /// Tested function return type
    /** Indicates the return type of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef func_ret_type return_type;

    /// Tested function arguments types
    /** Indicates the arguments types of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};

Więc jakie jest typowe użycie, które można z tego zrobić?

// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
    // Next line will declare the traits class
    // to detect the member function void foo(int,int) const
    DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}

// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>

// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   _this_.foo(a,b);
}

// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   default_foo(_this_,a,b);
}

// Let us declare test types
struct empty
{
};
struct direct_foo
{
    void foo(int,int);
};
struct direct_const_foo
{
    void foo(int,int) const;
};
struct inherited_const_foo :
    public direct_const_foo
{
};

// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
    int a;
    foo_bar(a); // calls default_foo

    empty b;
    foo_bar(b); // calls default_foo

    direct_foo c;
    foo_bar(c); // calls default_foo (member function is not const)

    direct_const_foo d;
    foo_bar(d); // calls d.foo (member function is const)

    inherited_const_foo e;
    foo_bar(e); // calls e.foo (inherited member function)
}
S. Paryż
źródło
5

Aby to osiągnąć, będziemy musieli użyć:

  1. Przeciążenie szablonu funkcji z różnymi typami zwracanymi w zależności od tego, czy metoda jest dostępna
  2. Zgodnie z meta-warunkami w type_traitsnagłówku, będziemy chcieli zwrócić true_typelubfalse_type z naszych przeciążeń
  3. Zadeklaruj true_typeprzeciążenie oczekujące inti false_typeprzeciążenie oczekujące wykorzystania parametrów Variadic: „Najniższy priorytet konwersji wielokropka w rozpoznawaniu przeciążenia”
  4. Definiując specyfikację szablonu dla true_typefunkcji, której będziemy używać declvali decltypepozwalając nam wykryć funkcję niezależnie od różnic w typach zwracanych lub przeciążeń między metodami

Możesz zobaczyć przykład na żywo tutaj .Ale wyjaśnię to również poniżej:

Chcę sprawdzić, czy istnieje funkcja o nazwie, testktóra pobiera konwertowalny typ int, wtedy musiałbym zadeklarować te dwie funkcje:

template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
  • decltype(hasTest<a>(0))::valuejest true(Uwaga, nie ma potrzeby tworzenia specjalnej funkcjonalności, aby poradzić sobie z void a::test()przeciążeniem, void a::test(int)jest akceptowana)
  • decltype(hasTest<b>(0))::valuejest true(ponieważ intmożna konwertować na double int b::test(double)jest akceptowane, niezależnie od typu zwracanego)
  • decltype(hasTest<c>(0))::valuejest false( cnie ma metody o nazwie, testktóra akceptuje typ konwertowany z inttego powodu, nie jest to akceptowane)

To rozwiązanie ma 2 wady:

  1. Wymaga deklaracji na metodę pary funkcji
  2. Tworzy zanieczyszczenie przestrzeni nazw, szczególnie jeśli chcemy przetestować pod kątem podobnych nazw, na przykład jak nazwalibyśmy funkcję, która chce przetestować test()metodę?

Dlatego ważne jest, aby te funkcje były zadeklarowane w przestrzeni nazw szczegółów lub idealnie, jeśli mają być używane tylko z klasą, powinny być deklarowane prywatnie przez tę klasę. W tym celu napisałem makro, które pomoże ci wyodrębnić te informacje:

#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
                              template <typename T> static false_type __ ## DEFINE(...); \
                              template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));

Możesz użyć tego jak:

namespace details {
    FOO(test(declval<int>()), test_int)
    FOO(test(), test_void)
}

Następnie wywołanie details::test_int<a>::valuelub details::test_void<a>::valueprzyniesie rezultat truelub falsedla celów kodu wbudowanego lub metaprogramowania.

Jonathan Mee
źródło
3

Aby nie przeszkadzać, możesz również umieścić serializew przestrzeni nazw klasy, która ma zostać serializowana, lub klasy archiwum, dzięki wyszukiwaniu Koeniga . Zobacz przestrzenie nazw dla bezpłatnych zastąpień funkcji uzyskać więcej informacji, . :-)

Otwarcie dowolnej przestrzeni nazw w celu zaimplementowania bezpłatnej funkcji jest po prostu błędne. (np. nie powinno się otwierać przestrzeni nazw stddo implementacji swapdla własnych typów, ale zamiast tego należy użyć wyszukiwania Koenig).

Chris Jester-Young
źródło
3

Wygląda na to, że chcesz mieć idiom wykrywacza. Powyższe odpowiedzi to warianty tego, które działają z C ++ 11 lub C ++ 14.

std::experimentalBiblioteka posiada cechy, które zasadniczo to zrobić. Przerabiając przykład z góry, może to być:

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

Jeśli nie możesz użyć std :: experimental, można stworzyć podstawową wersję w następujący sposób:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

Ponieważ has_serialize_t jest w rzeczywistości std :: true_type lub std :: false_type, można go używać za pośrednictwem dowolnego z popularnych idiomów SFINAE:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

Lub za pomocą wysyłki z rozpoznaniem przeciążenia:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}

gibon
źródło
2

W porządku. Drugie podejście. W porządku, jeśli tego też nie lubisz, szukam więcej pomysłów.

Artykuł Herba Suttera mówi o cechach. Możesz więc mieć klasę cech, której domyślna instancja ma zachowanie rezerwowe, a dla każdej klasy, w której istnieje funkcja składowa, klasa cech jest wyspecjalizowana do wywoływania funkcji składowej. Wydaje mi się, że artykuł Herba wspomina o technice robienia tego, aby nie wymagało to dużo kopiowania i wklejania.

Tak jak powiedziałem, być może nie chcesz dodatkowej pracy związanej z „tagowaniem” klas, które implementują tego członka. W takim przypadku szukam trzeciego rozwiązania ...

Chris Jester-Young
źródło
eh ... przeanalizowałem to rozwiązanie ... Myślę, że jest trochę za drogie dla użytkowników mojego frameworka. (ok, przyznaję, rozwijam framework do przesyłania strumieniowego i wybieram między rozszerzeniem iostream a przepisaniem czegoś łatwiejszego)
ugasoft
Moim trzecim rozwiązaniem byłoby użycie SFINAE. Ponieważ odpowiedź yrp już o tym wspomina, nie będę się w to zagłębiał (ponieważ wciąż go badam: znam pomysł, ale diabeł tkwi w szczegółach), chyba że jego rozwiązanie ostatecznie nie zadziała dla Ciebie . :-)
Chris Jester-Young
1

Bez obsługi C ++ 11 ( decltype) to mogłoby zadziałać:

SSCCE

#include <iostream>
using namespace std;

struct A { void foo(void); };
struct Aa: public A { };
struct B { };

struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };

template<typename T>
struct FooFinder {
    typedef char true_type[1];
    typedef char false_type[2];

    template<int>
    struct TypeSink;

    template<class U>
    static true_type &match(U);

    template<class U>
    static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);

    template<class U>
    static false_type &test(...);

    enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};

int main() {
    cout << FooFinder<A>::value << endl;
    cout << FooFinder<Aa>::value << endl;
    cout << FooFinder<B>::value << endl;

    cout << FooFinder<retA>::value << endl;
    cout << FooFinder<argA>::value << endl;
    cout << FooFinder<constA>::value << endl;
    cout << FooFinder<varA>::value << endl;
}

Jak to mam nadzieję działa

A, Aai Bsą klasami, o których mowa, Aabędąc specjalną, która dziedziczy członka, którego szukamy.

W i są zamiennikami korespondenta c ++ 11 klas. Również w celu zrozumienia metaprogramowania szablonów ujawniają podstawy sztuczki SFINAE-sizeof-trick.FooFindertrue_typefalse_type

TypeSinkJest struktura szablonu, która służy następnie do zlewu integralną wynik sizeofoperatora do wystąpienia szablonu, tworząc rodzaj.

matchFunkcja jest inny rodzaj SFINAE od szablonu, który jest pozostawiony bez generycznego odpowiednika. Dlatego można go utworzyć tylko wtedy, gdy typ jego argumentu pasuje do typu, dla którego został wyspecjalizowany.

Obie testfunkcje wraz z deklaracją wyliczenia ostatecznie tworzą centralny wzorzec SFINAE. Istnieje ogólny, który używa wielokropka, który zwraca false_typei odpowiednik z bardziej szczegółowymi argumentami, które mają pierwszeństwo.

Aby móc utworzyć instancję testfunkcji z argumentem szablonu o wartości T, matchnależy utworzyć instancję funkcji, ponieważ jej typ zwracany jest wymagany do utworzenia instancji TypeSinkargumentu. Zastrzeżenie polega na tym &U::foo, że będąc opakowanym w argument funkcji, nie odwołuje się do niego w ramach specjalizacji argumentów szablonu, więc dziedziczone wyszukiwanie elementu członkowskiego nadal ma miejsce.

Kamajii
źródło
1

Jeśli używasz szaleństwa na Facebooku, ich makro po wyjęciu z pudełka pomoże ci:

#include <folly/Traits.h>
namespace {
  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace

void some_func() {
  cout << "Does class Foo have a member int test() const? "
    << boolalpha << has_test_traits<Foo, int() const>::value;
}

Chociaż szczegóły implementacji są takie same, jak w poprzedniej odpowiedzi, korzystanie z biblioteki jest prostsze.

prehistoryczny pingwin
źródło
0

Miałem podobną potrzebę i natknąłem się na to SO. Zaproponowano tu wiele interesujących / potężnych rozwiązań, choć jest to trochę za długie ze względu na konkretną potrzebę: wykrycie, czy klasa ma funkcję składową z precyzyjną sygnaturą. Zrobiłem więc trochę czytania / testów i wymyśliłem moją wersję, która mogłaby być interesująca. Wykrywa:

  • statyczna funkcja składowa
  • niestatyczna funkcja składowa
  • niestatyczna funkcja składowa const

z precyzyjnym podpisem. Ponieważ nie muszę przechwytywać żadnego podpisu (wymagałoby to bardziej skomplikowanego rozwiązania), ten pasuje do mnie. Zasadniczo używał enable_if_t .

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

Wynik :

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1
ctNGUYEN
źródło
0

Opierając się na jrok „s odpowiedź , mam unikać korzystania zagnieżdżone klasy i / lub funkcje szablonów.

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

Możemy użyć powyższych makr jak poniżej:

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

Sugestie są mile widziane.

debashish.ghosh
źródło
0

Z c ++ 20 staje się to znacznie prostsze. Powiedzmy, że chcemy sprawdzić, czy klasa Tma funkcję składową void T::resize(typename T::size_type). Na przykład std::vector<U>ma taką funkcję członka. Następnie,

template<typename T>
concept has_resize_member_func = requires {
    typename T::size_type;
    { std::declval<T>().resize(std::declval<typename T::size_type>()) } -> std::same_as<void>;
};

i użycie jest

static_assert(has_resize_member_func<std::string>, "");
static_assert(has_resize_member_func<int> == false, "");
Peter Rindal
źródło