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 T
został toString()
zdefiniowany, to korzysta z niego; inaczej nie. Magiczną częścią, której nie umiem zrobić, jest część „FUNCTION_EXISTS”.
Odpowiedzi:
Tak, dzięki SFINAE możesz sprawdzić, czy dana klasa zapewnia określoną metodę. Oto działający kod:
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.
źródło
typeof
przezdecltype
przy użyciu C ++ 0x , na przykład poprzez -std = c ++ 0x.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:
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 środkudecltype
jest nieprawidłowe (inaczej mówiąc , funkcja nie istnieje).void()
Jest używany do zwracany typ wszystkich tych funkcjivoid
.0
Argumentem jest wykorzystywana do wolęos << obj
przeciążenia, gdy oba są dostępne (dosłowne0
jest od rodzajuint
i 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.
Przykład na żywo.
I na wyjaśnienia. Po pierwsze,
sfinae_true
jest typem pomocnika i zasadniczo jest równoznaczny z pisaniemdecltype(void(std::declval<T>().stream(a0)), std::true_type{})
. Zaletą jest to, że jest krótszy.Następnie
struct has_stream : decltype(...)
dziedziczy po jednymstd::true_type
lubstd::false_type
na końcu, w zależności od tego, czydecltype
rejestracjatest_stream
zakończy się niepowodzeniem, czy nie.Wreszcie,
std::declval
daje 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 jakdecltype
,sizeof
i inni.Pamiętaj, że
decltype
niekoniecznie jest to konieczne, ponieważsizeof
(i wszystkie nieocenione konteksty) uzyskało to rozszerzenie. Tyle, żedecltype
już zapewnia typ i jest po prostu czystszy. Otosizeof
wersja jednego z przeciążeń:int
Ilong
parametry są nadal tam z tego samego powodu. Wskaźnik tablicy służy do zapewnienia kontekstu, w którymsizeof
można go użyć.źródło
decltype
oversizeof
jest 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).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ąć?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):
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:
Zauważ jednak, że nie można po prostu wywołać tej
toString
funkcji 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):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::*)() const
wtedy wskaźnika jako typu funkcji składowej!).źródło
type_check
zapewnia 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 jakiSign
mogłaby zostać wywołana metoda z podpisem ? (Np. JeśliSign
=std::string(T::*)()
, pozwólstd::string T::toString(int default = 42, ...)
na dopasowanie.)T
nie 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 zis_class
kontrolą z podnieść.toString
funkcja jest szablonowa?C ++ 20 -
requires
wyrażeniaDo C ++ 20 dostarczane są koncepcje i różne narzędzia, takie jak
requires
wyrażenia, które są wbudowanym sposobem sprawdzania istnienia funkcji. Za ich pomocą możesz przepisać swojąoptionalToString
funkcję w następujący sposób: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
requires
wyrażeniami, ale nadal rozwiązuje problem w dość elegancki sposób. Zestaw narzędzi wprowadza niektóre metafunkcje, w tym,std::is_detected
które można wykorzystać do łatwego pisania na nim metafunkcji wykrywania typu lub funkcji. Oto jak możesz go użyć: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
: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:
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ć:Następnie można użyć
bool
do utworzenia czeku SFINAE.Wyjaśnienie
Makro
BOOST_TTI_HAS_MEMBER_FUNCTION
generuje metafunkcję,has_member_function_toString
któ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łonekvalue
zawiera,true
jeśli klasaT
ma funkcję członkastd::string toString()
.Alternatywnie,
has_member_function_toString
można przyjąć wskaźnik funkcji elementu jako parametr szablonu. Dlatego możliwe jest zastąpieniehas_member_function_toString<T, std::string>::value
przezhas_member_function_toString<std::string T::* ()>::value
.źródło
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
typeof
rozszerzenia: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 :)
źródło
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.Proste rozwiązanie dla C ++ 11:
Aktualizacja, 3 lata później: (i to nie zostało przetestowane). Aby przetestować istnienie, myślę, że to zadziała:
źródło
template<typename>
przeciążenia variadic: nie rozważano rozwiązania.Po to są cechy typu. Niestety muszą być zdefiniowane ręcznie. W twoim przypadku wyobraź sobie, co następuje:
źródło
&T::x
lub w sposób dorozumiany poprzez wiązanie go do odniesienia).type traits
kompilacji warunkowej w C ++ 11Cóż, 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.
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:
Poniższy przykład zaczerpnięty z N4502 pokazuje bardziej rozbudowaną sondę:
W porównaniu z innymi implementacjami opisanymi powyżej, ta jest dość prosta: wystarczy zredukowany zestaw narzędzi (
void_t
idetect
), 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 definicjivoid_t
( void_t na liście parametrów działa, ale nie jako typ zwracany ):źródło
To jest rozwiązanie C ++ 11 dla ogólnego problemu, jeśli „Gdybym zrobił X, czy skompilowałby?”
Cecha
has_to_string
taka, żehas_to_string<T>::value
jesttrue
wtedy i tylko wtedy, gdyT
ma metodę,.toString
którą można wywołać za pomocą 0 argumentów w tym kontekście.Następnie użyłbym wysyłania tagów:
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:
powyższe czynność polega na utworzeniu makra
MAKE_CODE_TRAIT
. Podajesz mu nazwę pożądanej cechy i kod, który może przetestować typT
. A zatem: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:
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.
źródło
has_to_string
Jednak nie będzie miało znaczenia, gdzie się wywołujesz .Oto kilka fragmentów opisujących użycie: * Odwaga od tego wszystkiego jest niższa
Sprawdź członka
x
w danej klasie. Może to być var, func, class, union lub enum:Sprawdź funkcję członka
void x()
:Sprawdź zmienną składową
x
:Sprawdź klasę członka
x
:Sprawdź związek członkowski
x
:Sprawdź wyliczenie członka
x
:Sprawdź dowolną funkcję członka
x
niezależnie od podpisu:LUB
Szczegóły i rdzeń:
Makra (El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
źródło
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?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
Zauważ, że sprawdza nawet stałość metody i działa również z typami pierwotnymi. (Mam na myśli
has_const_begin<int>::value
fałsz i nie powoduje błędu kompilacji).Przykład 2
Teraz szukamy podpisu:
void foo(MyClass&, unsigned)
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.
źródło
To była fajna łamigłówka - świetne pytanie!
Oto alternatywa dla rozwiązania Nicoli Bonelli, która nie polega na niestandardowym
typeof
operatorze.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:
Wygląda to tak, gdy MSVC lub Comeau biorą pod uwagę funkcję
toString()
członka polegającą naT
wyszukiwaniu nazwy w witrynie wywoławczej wdoToString()
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 ...
źródło
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.
źródło
struct g { void f(); private: void f(int); };
ponieważ jedna z funkcji jest prywatna (dzieje się takusing g::f;
, ponieważ kod działa , co powoduje, że zawodzi, jeśli jakakolwiekf
jest niedostępna).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
źródło
Przykład użycia SFINAE i częściowej specjalizacji szablonu, pisząc
Has_foo
sprawdzanie koncepcji:źródło
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.
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.
Możemy użyć powyższych 2 makr do wykonania kontroli has_typedef i has_mem_func jako:
źródło
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" ); };
Dziwny nikt nie zasugerował następującej fajnej sztuczki, którą widziałem kiedyś na tej samej stronie:
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.
źródło
Ogólny szablon, którego można użyć do sprawdzenia, czy niektóre „funkcje” są obsługiwane przez typ:
Szablon, który sprawdza, czy istnieje metoda
foo
zgodna z podpisemdouble(const char*)
Przykłady
http://coliru.stacked-crooked.com/a/83c6a631ed42cea4
źródło
has_foo
do wywołania szablonu zis_supported
. Co chciałbym to nazwać coś takiego:std::cout << is_supported<magic.foo(), struct1>::value << std::endl;
. Powód tego, chcę zdefiniowaćhas_foo
dla każdej innej sygnatury funkcji, którą chcę sprawdzić, zanim będę mógł sprawdzić funkcję?Co powiesz na to rozwiązanie?
źródło
toString
jest przeciążony, podobnie jak&U::toString
niejednoznaczny.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 ():
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”.
źródło
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.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:
Teraz możesz użyć tego w następujący sposób:
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.
źródło
Można pominąć wszystkie meta-programowanie w C ++ 14, i po prostu napisać to korzystając
fit::conditional
z Fit biblioteki:Możesz również utworzyć funkcję bezpośrednio z lambdas:
Jeśli jednak używasz kompilatora, który nie obsługuje ogólnych lambd, będziesz musiał napisać osobne obiekty funkcyjne:
źródło
fit
żadnej bibliotece innej niż standardowa?W C ++ 20 możesz napisać:
źródło
Oto przykład działającego kodu.
toStringFn<T>* = nullptr
włącza funkcję, która przyjmuje dodatkowyint
argument, który ma pierwszeństwo przed funkcją, która ma miejscelong
po wywołaniu z0
.Możesz użyć tej samej zasady dla funkcji, która zwraca,
true
jeśli funkcja jest zaimplementowana.źródło
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:
źródło
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.Przykład
źródło
źródło