Szablonowe sprawdzenie, czy istnieje funkcja członka klasy?


498

Czy można napisać szablon, który zmienia zachowanie w zależności od tego, czy określona funkcja elementu jest zdefiniowana w klasie?

Oto prosty przykład tego, co chciałbym napisać:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

Tak więc, jeśli class Tzostał toString()zdefiniowany, to korzysta z niego; inaczej nie. Magiczną częścią, której nie umiem zrobić, jest część „FUNCTION_EXISTS”.


6
Oczywiście nie trzeba dodawać, że poniższe odpowiedzi na szablony działają tylko z informacjami w czasie kompilacji, tzn. T musi mieć toString. Jeśli przekażesz podklasę T, która definiuje toString, ale T nie , zostaniesz o tym poinformowany, żeString nie jest zdefiniowany.
Alice Purcell,

Możliwa duplikat Jak sprawdzić, czy nazwa klasy (zmienna lub funkcja) istnieje w klasie, z określeniem typu lub bez? , ponieważ obejmuje szerszy problem z C ++ 03 do C ++ 1y.
iammilind

Odpowiedzi:


319

Tak, dzięki SFINAE możesz sprawdzić, czy dana klasa zapewnia określoną metodę. Oto działający kod:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

Właśnie przetestowałem to z Linuksem i gcc 4.1 / 4.3. Nie wiem, czy jest przenośny na inne platformy z różnymi kompilatorami.


18
Chociaż użyłem następującego wyrażenia dla „jeden” i „dwa”: typedef char Small; klasa Big {man dummy [2];}, aby zapewnić niejednoznaczność co do wielkości zmiennej zależnej od platformy.
user23167,

6
Wątpię, czy istnieje na ziemi platforma o rozmiarze (char) == sizeof (long)
Nicola Bonelli,

17
Nie jestem do końca pewien, ale nie sądzę, że jest to przenośne. typeof jest rozszerzeniem GCC, nie będzie działać na innych kompilatorach.
Leon Timmermans,

56
typeof nie jest potrzebny - char [sizeof (& C :: helloworld)] również działa. Aby uniknąć sizeof (long) == sizeof (char), użyj struct {char [2]} ;. Musi mieć rozmiar> = 2
MSalters

57
Trywialne, ale zajęło mi to trochę czasu, aby dowiedzieć się: zastąpienie typeofprzez decltypeprzy użyciu C ++ 0x , na przykład poprzez -std = c ++ 0x.
godz.

264

To pytanie jest stare, ale w C ++ 11 mamy nowy sposób sprawdzania istnienia funkcji (lub istnienia dowolnego nie-typu członka, naprawdę), ponownie polegając na SFINAE:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

Teraz kilka wyjaśnień. Po pierwsze, używam wyrażenia SFINAE, aby wykluczyć serialize(_imp)funkcje z rozdzielczości przeciążenia, jeśli pierwsze wyrażenie w środku decltypejest nieprawidłowe (inaczej mówiąc , funkcja nie istnieje).

void()Jest używany do zwracany typ wszystkich tych funkcji void.

0Argumentem jest wykorzystywana do wolę os << objprzeciążenia, gdy oba są dostępne (dosłowne 0jest od rodzaju inti jako taki pierwszego przeciążenia jest lepsze dopasowanie).


Prawdopodobnie chcesz, aby cecha sprawdziła, czy funkcja istnieje. Na szczęście łatwo to napisać. Uwaga jednak, że trzeba napisać cecha siebie za każdą inną nazwą funkcji może chcesz.

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Przykład na żywo.

I na wyjaśnienia. Po pierwsze, sfinae_truejest typem pomocnika i zasadniczo jest równoznaczny z pisaniem decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Zaletą jest to, że jest krótszy.
Następnie struct has_stream : decltype(...)dziedziczy po jednym std::true_typelub std::false_typena końcu, w zależności od tego, czy decltyperejestracja test_streamzakończy się niepowodzeniem, czy nie.
Wreszcie, std::declvaldaje ci „wartość” dowolnego rodzaju, który mijasz, bez potrzeby wiedzieć, jak ją zbudować. Należy pamiętać, że jest to możliwe tylko wewnątrz unevaluated kontekście, takich jak decltype, sizeofi inni.


Pamiętaj, że decltypeniekoniecznie jest to konieczne, ponieważ sizeof(i wszystkie nieocenione konteksty) uzyskało to rozszerzenie. Tyle, że decltypejuż zapewnia typ i jest po prostu czystszy. Oto sizeofwersja jednego z przeciążeń:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

intI longparametry są nadal tam z tego samego powodu. Wskaźnik tablicy służy do zapewnienia kontekstu, w którym sizeofmożna go użyć.


4
Zaletą decltypeover sizeofjest również to, że tymczasowe nie są wprowadzane przez specjalnie spreparowane reguły dla wywołań funkcji (więc nie musisz mieć praw dostępu do destruktora typu zwracanego i nie spowoduje domyślnej instancji, jeśli typ zwracany jest tworzenie instancji szablonu klasy).
Johannes Schaub - litb

5
Microsoft nie wdrożył jeszcze Expression SFINAE w swoim kompilatorze C ++. Pomyśl, że mogę pomóc zaoszczędzić czas niektórym ludziom, ponieważ byłem zdezorientowany, dlaczego to nie działało dla mnie. Ładne rozwiązanie, nie mogę się doczekać, aby użyć go w Visual Studio!
Jonathan

3
Twój pierwszy przykładowy link jest zepsuty
NathanOliver,

1
Trzeba powiedzieć, że static_assert(has_stream<X, char>() == true, "fail X");skompiluje się, a nie potwierdzi, ponieważ char można przekonwertować na int, więc jeśli to zachowanie nie jest pożądane i chce, aby wszystkie typy argumentów pasowały, to nie wiem, jak to osiągnąć?
Gabriel

4
Jeśli jesteś tak zdziwiony, jak ja na temat dwóch argumentów deklaracji: decltype naprawdę bierze tylko jeden; przecinek jest tutaj operatorem. Zobacz stackoverflow.com/questions/16044514/…
André

159

C ++ pozwala na użycie SFINAE do tego celu (zauważ, że w przypadku funkcji C ++ 11 jest to prostsze, ponieważ obsługuje rozszerzoną SFINAE na prawie dowolnych wyrażeniach - poniższe zostały przygotowane do pracy ze zwykłymi kompilatorami C ++ 03):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

powyższy szablon i makro próbują utworzyć szablon, nadając mu typ wskaźnika funkcji składowej i rzeczywisty wskaźnik funkcji składowej. Jeśli typy nie pasują, SFINAE powoduje zignorowanie szablonu. Użycie takie jak to:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

Zauważ jednak, że nie można po prostu wywołać tej toStringfunkcji w tym gałęzi if. ponieważ kompilator sprawdzi poprawność w obu gałęziach, to nie powiedzie się w przypadkach, gdy funkcja nie istnieje. Jednym ze sposobów jest ponowne użycie SFINAE (enable_if można również uzyskać z doładowania):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

Miłej zabawy z jego użyciem. Zaletą tego jest to, że działa również w przypadku przeciążonych funkcji składowych, a także dla stałych funkcji składowych (pamiętaj, aby użyć std::string(T::*)() constwtedy wskaźnika jako typu funkcji składowej!).


7
Podoba mi się sposób, w jaki type_checkzapewnia się, aby podpisy były zgodne. Czy istnieje sposób, aby to zrobić, aby pasowało do dowolnej metody, która mogłaby zostać wywołana w sposób, w jaki Signmogłaby zostać wywołana metoda z podpisem ? (Np. Jeśli Sign= std::string(T::*)(), pozwól std::string T::toString(int default = 42, ...)na dopasowanie.)
j_random_hacker

5
Po prostu wymyślam coś na ten temat, co nie było dla mnie od razu oczywiste, więc na wypadek, gdyby pomogło to innym: chk nie jest i nie musi być zdefiniowane! Operator sizeof określa rozmiar danych wyjściowych chk bez konieczności wywoływania chk.
SCFrench

3
@ deek0146: Tak, Tnie może być prymitywnym typem, ponieważ deklaracja typu wskaźnik do metody T nie podlega SFINAE i wystąpi błąd dla dowolnej klasy innej niż T. IMO najłatwiejszym rozwiązaniem jest połączenie z is_classkontrolą z podnieść.
Jan Hudec

2
Jak mogę to zrobić, jeśli moja toStringfunkcja jest szablonowa?
Frank,

4
Czy to (lub coś równoważnego) w trybie wzmocnienia?
Dan Nissenbaum

89

C ++ 20 - requireswyrażenia

Do C ++ 20 dostarczane są koncepcje i różne narzędzia, takie jak requireswyrażenia, które są wbudowanym sposobem sprawdzania istnienia funkcji. Za ich pomocą możesz przepisać swoją optionalToStringfunkcję w następujący sposób:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pre-C ++ 20 - Zestaw narzędzi do wykrywania

N4502 proponuje zestaw narzędzi do wykrywania do włączenia do standardowej biblioteki C ++ 17, który ostatecznie znalazł się w podstawach biblioteki TS v2. Najprawdopodobniej nigdy nie wejdzie w normę, ponieważ od tego czasu jest objęty requireswyrażeniami, ale nadal rozwiązuje problem w dość elegancki sposób. Zestaw narzędzi wprowadza niektóre metafunkcje, w tym, std::is_detectedktóre można wykorzystać do łatwego pisania na nim metafunkcji wykrywania typu lub funkcji. Oto jak możesz go użyć:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

Zauważ, że powyższy przykład nie został przetestowany. Zestaw narzędzi do wykrywania nie jest jeszcze dostępny w standardowych bibliotekach, ale propozycja zawiera pełną implementację, którą można łatwo skopiować, jeśli naprawdę jest to potrzebne. Dobrze gra z funkcją C ++ 17 if constexpr:

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return "toString not defined";
}

C ++ 14 - Boost.Hana

Boost.Hana najwyraźniej opiera się na tym konkretnym przykładzie i zapewnia rozwiązanie dla C ++ 14 w swojej dokumentacji, więc przytoczę go bezpośrednio:

[...] Hana zapewnia is_validfunkcję, którą można połączyć z ogólnymi lambdami C ++ 14, aby uzyskać znacznie czystszą implementację tej samej rzeczy:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Pozostaje nam obiekt funkcji, has_toStringktóry zwraca, czy podane wyrażenie jest poprawne w przekazanym mu argumencie. Wynik jest zwracany jako IntegralConstant, więc ciągłość nie jest tutaj problemem, ponieważ wynik funkcji jest reprezentowany jako typ. Teraz, oprócz tego, że jest mniej gadatliwy (to jedna linijka!), Intencja jest znacznie wyraźniejsza. Inne korzyści to fakt, że has_toStringmogą być przekazywane do algorytmów wyższego rzędu, a także można je zdefiniować w zakresie funkcji, więc nie ma potrzeby zanieczyszczania zakresu przestrzeni nazw szczegółami implementacji.

Boost.TTI

Kolejnym nieco idiomatycznym zestawem narzędzi do przeprowadzania takiej kontroli - choć mniej eleganckiej - jest Boost.TTI , wprowadzony w Boost 1.54.0. Na przykład musisz użyć makra BOOST_TTI_HAS_MEMBER_FUNCTION. Oto jak możesz go użyć:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

Następnie można użyć booldo utworzenia czeku SFINAE.

Wyjaśnienie

Makro BOOST_TTI_HAS_MEMBER_FUNCTIONgeneruje metafunkcję, has_member_function_toStringktóra przyjmuje sprawdzony typ jako pierwszy parametr szablonu. Drugi parametr szablonu odpowiada typowi zwracanemu funkcji składowej, a następujące parametry odpowiadają typom parametrów funkcji. Członek valuezawiera, truejeśli klasa Tma funkcję członka std::string toString().

Alternatywnie, has_member_function_toStringmożna przyjąć wskaźnik funkcji elementu jako parametr szablonu. Dlatego możliwe jest zastąpienie has_member_function_toString<T, std::string>::valueprzez has_member_function_toString<std::string T::* ()>::value.


1
bardziej zwięzłe niż 03
ZFY

@ZFY Myślę, że Boost.TTI działa również z C ++ 03, ale jest to najmniej eleganckie rozwiązanie.
Morwenn

Czy rozwiązanie C ++ 20 jest naprawdę poprawne? Chciałbym - ale jest to odrzucone przez g ++ i msvc - akceptowane tylko przez clang.
Bernd Baumanns

na cppreferencji można przeczytać: Jeśli wyrażenie-wyrażenie zawiera niepoprawne typy lub wyrażenia w swoich wymaganiach i nie pojawia się w deklaracji encji szablonowej, wówczas program jest źle sformułowany.
Bernd Baumanns

@BerndBaumanns Naprawdę? Mam go do pracy z łączem GCC: godbolt.org/z/CBwZdE Może masz rację, sprawdziłem tylko, czy działa, ale nie sprawdziłem, czy jest zgodny z prawem zgodnie ze standardowym brzmieniem.
Morwenn

56

Chociaż to pytanie ma dwa lata, odważę się dodać moją odpowiedź. Mamy nadzieję, że wyjaśni to poprzednie, bezsprzecznie doskonałe rozwiązanie. Wziąłem bardzo pomocne odpowiedzi Nicoli Bonelli i Johannesa Schauba i połączyłem je w rozwiązanie, które według IMHO jest bardziej czytelne, jasne i nie wymaga typeofrozszerzenia:

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

Sprawdziłem to za pomocą gcc 4.1.2. Podziękowania należą się głównie Nicoli Bonelli i Johannesowi Schaubowi, więc oddaj im głos, jeśli moja odpowiedź ci pomoże :)


1
Zastanawiam się, czy to robi coś, czego nie robi poniższe rozwiązanie Konrada Rudolfa?
Alastair Irvine,

3
@AlastairIrvine, to rozwiązanie ukrywa całą logikę wewnątrz, Konrad nakłada pewne obciążenia na użytkownika. Chociaż rozwiązanie Konrada jest krótkie i znacznie bardziej czytelne, wymaga osobnej specjalizacji szablonu dla każdej klasy, która ma toString. Jeśli napiszesz ogólną bibliotekę, która chce pracować z dowolną klasą (pomyśl o czymś takim jak ulepszenie), wymaganie od użytkownika zdefiniowania dodatkowych specjalizacji niektórych niejasnych szablonów może być nie do przyjęcia. Czasami lepiej jest napisać bardzo skomplikowany kod, aby interfejs publiczny był tak prosty, jak to tylko możliwe.
FireAphis

30

Proste rozwiązanie dla C ++ 11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Aktualizacja, 3 lata później: (i to nie zostało przetestowane). Aby przetestować istnienie, myślę, że to zadziała:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

4
Jest to prosty i elegancki, ale ściśle rzecz biorąc nie ma odpowiedzi na pytanie OP: nie włączyć rozmówcę do sprawdzenia istnienia funkcji użytkownika, zawsze dostarczać go. Ale i tak miło.
Adrian W

@AdrianW, dobry punkt. Zaktualizowałem swoją odpowiedź. Nie przetestowałem tego jednak
Aaron McDaid,

Gdyby to pomogło komuś innemu, nie mogłem tego zrobić bez template<typename>przeciążenia variadic: nie rozważano rozwiązania.
Laboratorio Cobotica,

Ponownie jest to niepoprawny C ++ 11.
Peter

29

Po to są cechy typu. Niestety muszą być zdefiniowane ręcznie. W twoim przypadku wyobraź sobie, co następuje:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

5
powinieneś preferować wyliczanie dla cech zamiast stałych statycznych: „Stałe elementy statyczne są wartościami lv, co zmusza kompilator do utworzenia instancji i przydzielenia definicji dla elementu statycznego. W rezultacie obliczenia nie są już ograniczone do czystego„ czasu kompilacji „efekt”.
Özgür

5
„Wartości wyliczenia nie są wartościami lv (to znaczy, że nie mają adresu). Więc kiedy je przekażesz„ przez odniesienie ”,„ pamięć statyczna nie jest używana. Jest prawie dokładnie tak, jakbyś przekazał obliczoną wartość jako literał Te rozważania motywują nas do korzystania z wartości wyliczeniowych "Szablony C ++: Kompletny przewodnik
Özgür

22
Comptrol: nie, cytowany fragment nie ma tutaj zastosowania, ponieważ stałe statyczne typu całkowitego są szczególnym przypadkiem! Zachowują się tutaj dokładnie jak wyliczanka i są preferowanym sposobem. Stary hack enum był konieczny tylko na kompilatorach, które nie były zgodne ze standardem C ++.
Konrad Rudolph

3
@Roger Pate: Niezupełnie. „Użyty w programie” tutaj jest najwyraźniej synonimem „odniesienia”. Przeważającym czytaniem tego fragmentu i tego, który został zaimplementowany we wszystkich współczesnych kompilatorach C ++, jest to, że można przyjąć wartość stałej statycznej bez konieczności jej deklarowania (poprzednie zdanie mówi: „… element może występować w wyrażeniach stałych stałych … ”). Ty tylko trzeba określić go jeśli wziąć swój adres (poprzez wyraźnie &T::xlub w sposób dorozumiany poprzez wiązanie go do odniesienia).
Konrad Rudolph


25

Cóż, to pytanie ma już długą listę odpowiedzi, ale chciałbym podkreślić komentarz Morwenna: istnieje propozycja dla C ++ 17, która sprawia, że ​​jest to o wiele prostsze. Szczegółowe informacje można znaleźć w N4502 , ale jako samodzielny przykład rozważ następujące kwestie.

Ta część jest częścią stałą, umieść ją w nagłówku.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

następnie jest część zmienna, w której określasz to, czego szukasz (typ, typ członka, funkcja, funkcja członka itp.). W przypadku PO:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

Poniższy przykład zaczerpnięty z N4502 pokazuje bardziej rozbudowaną sondę:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

W porównaniu z innymi implementacjami opisanymi powyżej, ta jest dość prosta: wystarczy zredukowany zestaw narzędzi ( void_ti detect), nie potrzeba owłosionych makr. Poza tym zgłoszono (patrz N4502 ), że jest on wymiernie bardziej wydajny (czas kompilacji i zużycie pamięci kompilatora) niż poprzednie podejścia.

Oto przykład na żywo . Działa dobrze z Clangiem, ale niestety wersje GCC wcześniejsze niż 5.1 zastosowały inną interpretację standardu C ++ 11, co spowodowało, void_tże nie działało zgodnie z oczekiwaniami. Yakk już zapewnił obejście: użyj następującej definicji void_t( void_t na liście parametrów działa, ale nie jako typ zwracany ):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

Czy można go rozszerzyć, aby wykrywał funkcje nie będące członkami?
plasmacel,

Tak, oczywiście. Przyjrzyj się dokładnie przykładom: w zasadzie podajesz wyrażenie i sprawdzasz, czy jest poprawne. Nic nie wymaga, aby to wyrażenie dotyczyło tylko wywołania funkcji składowej.
akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) to sposób na przyszłość ... Szukałem zgrabnego sposobu wykrywania rzeczy na typach, a N4502 to sposób iść.
tlonuk

11

To jest rozwiązanie C ++ 11 dla ogólnego problemu, jeśli „Gdybym zrobił X, czy skompilowałby?”

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

Cecha has_to_stringtaka, że has_to_string<T>::valuejest truewtedy i tylko wtedy, gdy Tma metodę, .toStringktórą można wywołać za pomocą 0 argumentów w tym kontekście.

Następnie użyłbym wysyłania tagów:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

który jest łatwiejszy w utrzymaniu niż złożone wyrażenia SFINAE.

Możesz napisać te cechy za pomocą makra, jeśli okaże się, że robisz to dużo, ale są one stosunkowo proste (po kilka wierszy), więc może nie warto:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

powyższe czynność polega na utworzeniu makra MAKE_CODE_TRAIT. Podajesz mu nazwę pożądanej cechy i kod, który może przetestować typ T. A zatem:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

tworzy powyższą klasę cech.

Nawiasem mówiąc, powyższa technika jest częścią tego, co MS nazywa „wyrażeniem SFINAE”, a ich kompilator z 2013 roku zawiedzie dość mocno.

Zauważ, że w C ++ 1y możliwa jest następująca składnia:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

która jest wbudowaną gałęzią warunkową kompilacji, która nadużywa wielu funkcji C ++. Takie postępowanie prawdopodobnie nie jest tego warte, ponieważ korzyść (wbudowany kod) nie jest warta kosztów (prawie nikt nie rozumie, jak to działa), ale istnienie powyższego rozwiązania może być interesujące.


Czy dotyczy to spraw prywatnych?
wieża120

@ tower120 Musiałbym eksperymentować: sposób, w jaki szablony współdziałają z prywatną / publiczną / chronioną, jest dla mnie trochę niejasny. has_to_stringJednak nie będzie miało znaczenia, gdzie się wywołujesz .
Yakk - Adam Nevraumont

ale wiesz, jeśli spojrzysz z drugiej strony ... Możemy dotrzeć do chronionych członków z klasy Derived. Może jeśli wstawię te wszystkie rzeczy do klasy INSIDE i przekonwertujesz z struktur na funkcje constexpr ...
tower120

Spójrz na to coliru.stacked-crooked.com/a/ee94d16e7c07e093 Po prostu nie mogę tego zrobić constexpr
tower120

@ tower120 C ++ 1y sprawia, że ​​działa: coliru.stacked-crooked.com/a/d8cdfff24a171394
Yakk - Adam Nevraumont

10

Oto kilka fragmentów opisujących użycie: * Odwaga od tego wszystkiego jest niższa

Sprawdź członka xw 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łonkowski x:

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

Sprawdź wyliczenie członka x:

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

Sprawdź dowolną funkcję członka xniezależ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)

1
czy masz jakiś pomysł, dlaczego przechodzimy sig_check<func_sig, &T::func_name>na bezpłatne sprawdzanie funkcji: sig_check<func_sig, &func_name>nie buduje się z „niezadeklarowanym identyfikatorem”, w którym jest mowa o nazwie funkcji, którą chcemy sprawdzić? ponieważ spodziewałbym się, że SFINAE NIE spowoduje błędu, robi to tylko dla członków, dlaczego nie dla darmowych funkcji?
v.oddou

Zakładam, że miałoby to coś wspólnego z faktem, że darmowa funkcja nie jest klasą ani strukturą. Ta technika dedukcji obecności członka naprawdę koncentruje się na mechanizmie wielokrotnego dziedziczenia w C ++, wymuszając niejednoznaczność między klasą pośredniczącą, która istnieje tylko w celu hostowania członka, którego szukasz, w porównaniu z klasą, którą faktycznie sprawdzasz dla członka w. To interesujące pytanie, ale nie myślałem o tym. Możesz sprawdzić inne techniki sprawdzania członków w C ++ 11/14. W nowym standardzie widziałem kilka sprytnych rzeczy.
Brett Rossier

Dzięki za odpowiedź, myślę, że być może będę musiał bardziej szczegółowo sprawdzić dane wywiadowcze dotyczące dziedziczenia, ponieważ do tej pory nie widziałem żadnej korelacji między po prostu poleganiem na SFINAE w celu stworzenia wyrażenia, które nie byłoby poprawne, wyrażając dostęp do element członkowski w parametrze typu szablonu i wielokrotne dziedziczenie. Ale całkowicie wierzę, że w C ++ nawet odległe koncepcje mogą się nawzajem krwawić. Teraz, jeśli chodzi o bezpłatne funkcje, to pytanie jest interesujące: stackoverflow.com/questions/26744589 Odpowiedź TC wydaje się
polegać

8

Odpowiedziałem na to w innym wątku, który (w przeciwieństwie do powyższych rozwiązań) sprawdza również odziedziczone funkcje składowe:

SFINAE do sprawdzania dziedziczonych funkcji składowych

Oto kilka przykładów z tego rozwiązania:

Przykład 1:

Sprawdzamy członka z następującym podpisem: T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

Zauważ, że sprawdza nawet stałość metody i działa również z typami pierwotnymi. (Mam na myśli has_const_begin<int>::valuefałsz i nie powoduje błędu kompilacji).

Przykład 2

Teraz szukamy podpisu: void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

Zauważ, że MyClass nie musi być domyślnie konstruowalny ani spełniać żadnej specjalnej koncepcji. Ta technika działa również z elementami szablonu.

Z niecierpliwością czekam na opinie w tej sprawie.


7

To była fajna łamigłówka - świetne pytanie!

Oto alternatywa dla rozwiązania Nicoli Bonelli, która nie polega na niestandardowym typeofoperatorze.

Niestety nie działa na GCC (MinGW) 3.4.5 lub Digital Mars 8.42n, ale działa na wszystkich wersjach MSVC (w tym VC6) i Comeau C ++.

Dłuższy blok komentarza zawiera szczegółowe informacje na temat jego działania (lub powinien działać). Jak mówi, nie jestem pewien, które zachowanie jest zgodne ze standardami - chętnie skomentuję to.


aktualizacja - 7 listopada 2008:

Wygląda na to, że chociaż kod ten jest poprawny pod względem składniowym, zachowanie pokazane przez MSVC i Comeau C ++ nie jest zgodne ze standardem (dzięki Leonowi Timmermansowi i litbowi za skierowanie mnie we właściwym kierunku). Standard C ++ 03 mówi:

14.6.2 Nazwy zależne [temp.dep]

Ustęp 3

W definicji szablonu klasy lub elementu szablonu klasy, jeśli klasa podstawowa szablonu klasy zależy od parametru-szablonu, zakres klasy podstawowej nie jest badany podczas wyszukiwania nazwy bez kwalifikacji ani w punkcie definicji klasy szablon lub element lub podczas tworzenia szablonu klasy lub elementu.

Wygląda to tak, gdy MSVC lub Comeau biorą pod uwagę funkcję toString()członka polegającą na Twyszukiwaniu nazwy w witrynie wywoławczej w doToString()momencie tworzenia szablonu, jest to niepoprawne (nawet jeśli w tym przypadku szukałem takiego zachowania).

Zachowanie GCC i Digital Mars wydaje się prawidłowe - w obu przypadkach toString()funkcja nie będąca członkiem jest związana z wywołaniem.

Szczury - myślałem, że mogłem znaleźć sprytne rozwiązanie, zamiast tego odkryłem kilka błędów kompilatora ...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}

1
Nie, to nie jest zgodne ze standardami, ale myślę, że będzie działać w GCC, jeśli włączysz opcję -fpermissive.
Leon Timmermans,

Wiem, że komentarze nie dają dużo miejsca, ale czy możesz wskazać informacje, dlaczego nie jest ono zgodne ze standardami? (Nie kłócę się - jestem ciekawy)
Michael Burr

Mike B: standard mówi w 3.10 p15: „Jeśli program próbuje uzyskać dostęp do przechowywanej wartości obiektu przez wartość inną niż jeden z następujących typów, zachowanie jest niezdefiniowane”, a ta lista rzeczywiście nie obejmuje przypadku zrobić.
Johannes Schaub - litb

4
nie jestem pewien, dlaczego nie dodaje kolejnego komentarza: twoje wywołanie toString jest niekwalifikowane. dlatego zawsze będzie wywoływać funkcję swobodną, ​​a nigdy tę podstawową, ponieważ klasa podstawowa zależy od parametru typu szablonu.
Johannes Schaub - litb

@litb: Dzięki za wskazówki. Nie sądzę, że 3.10 ma tutaj zastosowanie. Wywołanie metody toString () wewnątrz metody doToString () nie oznacza „dostępu do przechowywanej wartości obiektu przez wartość”. Ale twój drugi komentarz jest poprawny. Zaktualizuję odpowiedź.
Michael Burr,

6

Standardowe rozwiązanie C ++ przedstawione tutaj przez litb nie będzie działać zgodnie z oczekiwaniami, jeśli metoda zostanie zdefiniowana w klasie bazowej.

Aby uzyskać rozwiązanie tego problemu, zapoznaj się z:

W języku rosyjskim: http://www.rsdn.ru/forum/message/2759773.1.aspx

Tłumaczenie na angielski Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

Jest niesamowicie sprytny. Jednak jednym z problemów związanych z tym rozwiązaniem jest to, że daje błędy kompilatora, jeśli testowany typ jest takim, którego nie można użyć jako klasy podstawowej (np. Typy pierwotne)

W Visual Studio zauważyłem, że jeśli pracuje się z metodą bez argumentów, wokół argumentów należy wstawić dodatkową parę redundantną (), aby wydedukować () w wyrażeniu sizeof.


Hmm, po opracowaniu własnej wersji przy użyciu pomysłów na posty, odkryłem, że pomysł ma jeszcze inne wady, więc ponownie usunąłem kod z mojej odpowiedzi. Jednym z nich jest to, że wszystkie funkcje muszą być publiczne w typie docelowym. Nie można więc sprawdzić funkcji „f” w tym: struct g { void f(); private: void f(int); };ponieważ jedna z funkcji jest prywatna (dzieje się tak using g::f;, ponieważ kod działa , co powoduje, że zawodzi, jeśli jakakolwiek fjest niedostępna).
Johannes Schaub - litb

6

MSVC ma słowa kluczowe __if_exists i __if_not_exists ( Doc ). Wraz z podejściem typuof-SFINAE Nicoli mogłem stworzyć czek dla GCC i MSVC, tak jak szukał PO.

Aktualizacja: Źródło można znaleźć tutaj


6

Przykład użycia SFINAE i częściowej specjalizacji szablonu, pisząc Has_foosprawdzanie koncepcji:

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

template<typename T, typename = void> struct Has_foo: std::false_type{};

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};


static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

5

Zmodyfikowałem rozwiązanie dostarczone w https://stackoverflow.com/a/264088/2712152 aby uczynić go nieco bardziej ogólnym. Ponieważ nie używa żadnej z nowych funkcji C ++ 11, możemy go używać ze starymi kompilatorami i powinien również działać z msvc. Ale kompilatory powinny umożliwić C99 korzystanie z tego, ponieważ używa makr variadic.

Poniższego makra można użyć do sprawdzenia, czy dana klasa ma określony typefef, czy nie.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Poniższego makra można użyć do sprawdzenia, czy dana klasa ma określoną funkcję składową, czy nie z dowolną liczbą argumentów.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Możemy użyć powyższych 2 makr do wykonania kontroli has_typedef i has_mem_func jako:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

Możesz to poprawić, aby obsługiwać funkcje składowe za pomocą argumentów szablonu. Zmień szablon <typename T> na szablon <typename T, typename ... Args>, a następnie możesz użyć „Args ...” w elipsis makra, aby utworzyć strukturę czeku z różnymi argumentami szablonu. na przykład. Wykryj metodę „void onNext (const T &)” HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic

4

Dziwny nikt nie zasugerował następującej fajnej sztuczki, którą widziałem kiedyś na tej samej stronie:

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

Musisz upewnić się, że T jest klasą. Wydaje się, że dwuznaczność w wyszukiwaniu foo jest błędem podstawienia. Uczyniłem go działającym na gcc, ale nie jestem pewien, czy jest to standard.


3

Ogólny szablon, którego można użyć do sprawdzenia, czy niektóre „funkcje” są obsługiwane przez typ:

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

Szablon, który sprawdza, czy istnieje metoda foozgodna z podpisemdouble(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

Przykłady

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4


Czy istnieje sposób na wstawienie has_foodo wywołania szablonu z is_supported. Co chciałbym to nazwać coś takiego: std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. Powód tego, chcę zdefiniować has_foodla każdej innej sygnatury funkcji, którą chcę sprawdzić, zanim będę mógł sprawdzić funkcję?
CJCombrink

2

Co powiesz na to rozwiązanie?

#include <type_traits>

template <typename U, typename = void> struct hasToString : std::false_type { };

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

Nie powiedzie się, jeśli toStringjest przeciążony, podobnie jak &U::toStringniejednoznaczny.
Yakk - Adam Nevraumont

@Yakk Myślę, że obsada może rozwiązać ten problem.
user1095108

2

Tutaj jest wiele odpowiedzi, ale nie udało mi się znaleźć wersji, która wykonuje porządkowanie rozdzielczości rzeczywistej , nie używając żadnej z nowszych funkcji c ++ (tylko przy użyciu funkcji c ++ 98).
Uwaga: Ta wersja jest testowana i działa z vc ++ 2013, g ++ 5.2.0 i kompilatorem onlline.

Więc wymyśliłem wersję, która używa tylko sizeof ():

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

Wersja demonstracyjna na żywo (z rozszerzonym sprawdzaniem typu zwrotu i obejściem vc ++ 2010): http://cpp.sh/5b2vs

Brak źródła, ponieważ sam to wymyśliłem.

Podczas uruchamiania wersji demonstracyjnej Live na kompilatorze g ++ pamiętaj, że dozwolone są rozmiary macierzy 0, co oznacza, że ​​użyty static_assert nie spowoduje błędu kompilatora, nawet jeśli się nie powiedzie.
Często używanym obejściem jest zamiana „typedef” w makrze na „extern”.


Nie, ale ogłaszam to sam i nie używa wartości rvalue (spójrz na górę mojego kodu). Lub możesz po prostu przekonać się i wypróbować demo na żywo w trybie c ++ 98. PS: static_assert też nie jest c ++ 98, ale są obejścia (demo na żywo)
user3296587

och! przegapiłem to. :-)
Ian Ni-Lewis

Twoje twierdzenia statyczne nie działają. Musisz użyć rozmiaru tablicy -1 zamiast 0 (spróbuj wstawić static_assert(false);). Użyłem tego w połączeniu z CRTP, gdzie chcę ustalić, czy klasa pochodna ma określoną funkcję - która okazuje się nie działać, ale twoje twierdzenia zawsze mijały. Straciłem do tego włosy.
świnie

Zakładam, że używasz g ++. Należy pamiętać, że gcc / g ++ ma rozszerzenie, które pozwala na tablicę zerowej wielkości ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
3296587

Czy możesz to przepisać, aby nie przeciążać operatora? np. wybrać innego operatora? Ponadto unikaj zanieczyszczenia przestrzeni nazw czymkolwiek innym niż has_awesome_member?
einpoklum,

1

Oto moja wersja, która obsługuje wszystkie możliwe przeciążenia funkcji składowych dowolną liczbą, w tym funkcje składowe szablonu, ewentualnie z domyślnymi argumentami. Wyróżnia 3 wzajemnie wykluczające się scenariusze podczas wywoływania funkcji składowej do pewnego typu klasy, z podanymi typami argumentów: (1) poprawna, (2) niejednoznaczna lub (3) nieopłacalna. Przykładowe użycie:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

Teraz możesz użyć tego w następujący sposób:

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

Oto kod napisany w c ++ 11, jednak możesz go łatwo przenieść (z drobnymi poprawkami) na wersję inną niż c ++ 11, która ma rozszerzenia typeof (np. Gcc). Możesz zastąpić makro HAS_MEM swoim własnym.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif


1

Można pominąć wszystkie meta-programowanie w C ++ 14, i po prostu napisać to korzystając fit::conditionalz Fit biblioteki:

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

Możesz również utworzyć funkcję bezpośrednio z lambdas:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

Jeśli jednak używasz kompilatora, który nie obsługuje ogólnych lambd, będziesz musiał napisać osobne obiekty funkcyjne:

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);

1
Jak łatwo jest to napisać, aby nie musieć polegać na fitżadnej bibliotece innej niż standardowa?
einpoklum,

1

W C ++ 20 możesz napisać:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

Oto przykład działającego kodu.

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptrwłącza funkcję, która przyjmuje dodatkowy intargument, który ma pierwszeństwo przed funkcją, która ma miejsce longpo wywołaniu z 0.

Możesz użyć tej samej zasady dla funkcji, która zwraca, truejeśli funkcja jest zaimplementowana.

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}

0

Miałem podobny problem:

Klasa szablonów, która może pochodzić z kilku klas bazowych, z których niektóre mają określonego członka, a inne nie.

Rozwiązałem go podobnie do odpowiedzi „typeof” (Nicola Bonelli), ale z decltype, więc kompiluje się i działa poprawnie na MSVS:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

0

Jeszcze jeden sposób, aby to zrobić w C ++ 17 (zainspirowany przez boost: hana).

Napisz to raz i używaj wiele razy. Nie wymaga has_something<T>klas cech typu.

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Przykład

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

6
„Nie potrzebujemy opisów odpowiedzi” ... proszę dodać kilka informacji do odpowiedzi, aby ją poprawić. Dzięki.
YesThatIsMyName
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.