W Javie możesz zdefiniować klasę ogólną, która akceptuje tylko typy rozszerzające wybraną przez Ciebie klasę, np .:
public class ObservableList<T extends List> {
...
}
Odbywa się to za pomocą słowa kluczowego „extends”.
Czy istnieje jakiś prosty odpowiednik tego słowa kluczowego w C ++?
Odpowiedzi:
Sugeruję użycie statycznej funkcji asercji Boost w połączeniu z
is_base_of
biblioteką cech typu Boost:W innych, prostszych przypadkach możesz po prostu zadeklarować szablon globalny, ale zdefiniować go (jawnie lub częściowo) tylko dla prawidłowych typów:
[Drobne EDYCJA 12.06.2013: Użycie zadeklarowanego, ale niezdefiniowanego szablonu spowoduje wyświetlenie komunikatów o błędach przez konsolidator , a nie kompilator.]
źródło
myBaseType
dokładnie. Przed odrzuceniem Boost powinieneś wiedzieć, że większość z nich to kod szablonu zawierający tylko nagłówki - więc nie ma kosztu pamięci ani czasu w czasie wykonywania dla rzeczy, których nie używasz. Również konkretne rzeczy, których użyjesz tutaj (BOOST_STATIC_ASSERT()
iis_base_of<>
), mogą być zaimplementowane przy użyciu tylko deklaracji (tj. Bez rzeczywistych definicji funkcji lub zmiennych), więc nie zajmują również miejsca ani czasu.static_assert(std::is_base_of<List, T>::value, "T must extend list")
.my_template<int> x;
lubmy_template<float**> y;
i sprawdzić, czy kompilator na to zezwala, a następnie zadeklarować zmiennąmy_template<char> z;
i sprawdzić, czy nie.Zwykle jest to nieuzasadnione w C ++, jak zauważyły inne odpowiedzi. W C ++ mamy tendencję do definiowania typów ogólnych na podstawie innych ograniczeń innych niż „dziedziczy z tej klasy”. Jeśli naprawdę chciałeś to zrobić, możesz to zrobić w C ++ 11 i
<type_traits>
:To jednak łamie wiele koncepcji, których ludzie oczekują w C ++. Lepiej jest używać sztuczek, takich jak definiowanie własnych cech. Na przykład, może
observable_list
chce przyjąć każdy rodzaj kontenera, który ma typedefsconst_iterator
orazbegin
iend
funkcję członka, który powracaconst_iterator
. Jeśli ograniczysz to do klas, które dziedzicząlist
po tym czasie, użytkownik, który ma własny typ, który nie dziedziczy,list
ale udostępnia te funkcje składowe, a typedefs nie będzie mógł użyć twojegoobservable_list
.Istnieją dwa rozwiązania tego problemu, jednym z nich jest nie ograniczanie niczego i poleganie na pisaniu na klawiaturze. Dużą wadą tego rozwiązania jest to, że wiąże się ono z ogromną liczbą błędów, które mogą być trudne do zrozumienia dla użytkowników. Innym rozwiązaniem jest zdefiniowanie cech w celu ograniczenia podanego typu w celu spełnienia wymagań interfejsu. Dużą wadą tego rozwiązania jest to, że wymaga dodatkowego pisania, co może być postrzegane jako denerwujące. Jednak pozytywną stroną jest to, że będziesz w stanie pisać własne komunikaty o błędach a la
static_assert
.Dla kompletności podano rozwiązanie powyższego przykładu:
W powyższym przykładzie pokazano wiele koncepcji, które pokazują funkcje C ++ 11. Niektóre terminy wyszukiwania dla ciekawskich to szablony wariadyczne, SFINAE, wyrażenie SFINAE i cechy typu.
źródło
template<class T:list>
jest to tak obraźliwa koncepcja. Dzięki za wskazówkę.Prostym rozwiązaniem, o którym nikt jeszcze nie wspomniał, jest po prostu zignorowanie problemu. Jeśli spróbuję użyć
int
typu szablonu jako szablonu w szablonie funkcji, który oczekuje klasy kontenera, takiej jak wektor lub lista, otrzymam błąd kompilacji. Surowy i prosty, ale rozwiązuje problem. Kompilator spróbuje użyć określonego typu, a jeśli to się nie powiedzie, wygeneruje błąd kompilacji.Jedynym problemem jest to, że wyświetlane komunikaty o błędach będą trudne do odczytania. Niemniej jednak jest to bardzo powszechny sposób. Biblioteka standardowa jest pełna szablonów funkcji lub klas, które oczekują określonego zachowania od typu szablonu i nie robią nic, aby sprawdzić, czy użyte typy są prawidłowe.
Jeśli chcesz ładniejszych komunikatów o błędach (lub chcesz wychwycić przypadki, które nie powodowałyby błędu kompilatora, ale nadal nie mają sensu), możesz, w zależności od tego, jak skomplikowane chcesz to uczynić, użyć statycznego potwierdzenia Boost lub Biblioteka Boost concept_check.
Z aktualnym kompilatorem masz wbudowany plik built_in
static_assert
, którego można by użyć zamiast tego.źródło
T
i skąd jest wywoływany ten kod? Bez kontekstu nie mam szans zrozumieć tego fragmentu kodu. Ale to, co powiedziałem, jest prawdą. Jeśli spróbujesz wywołaćtoString()
typ, który nie matoString
funkcji składowej, otrzymasz błąd kompilacji.Możemy użyć
std::is_base_of
istd::enable_if
:(
static_assert
można usunąć, powyższe klasy mogą być zaimplementowane na zamówienie lub używane z boost, jeśli nie możemy się odwołaćtype_traits
)źródło
O ile wiem, nie jest to obecnie możliwe w C ++. Istnieją jednak plany dodania funkcji zwanej „koncepcjami” w nowym standardzie C ++ 0x, która zapewni funkcjonalność, której szukasz. Ten artykuł w Wikipedii o C ++ Concepts wyjaśni to bardziej szczegółowo.
Wiem, że to nie rozwiązuje twojego bezpośredniego problemu, ale jest kilka kompilatorów C ++, które już zaczęły dodawać funkcje z nowego standardu, więc może być możliwe znalezienie kompilatora, który już zaimplementował funkcję koncepcyjną.
źródło
static_assert
i SFINAE, jak pokazują inne odpowiedzi. Pozostała kwestia dla kogoś pochodzącego z Javy, C # lub Haskella (...) polega na tym, że kompilator C ++ 20 nie sprawdza definicji pod kątem wymaganych pojęć, co robią Java i C #.Myślę, że wszystkie wcześniejsze odpowiedzi straciły las z oczu.
Typy Java to nie to samo, co szablony ; używają wymazywania typów , które jest techniką dynamiczną , zamiast polimorfizmu czasu kompilacji , który jest techniką statyczną . Powinno być oczywiste, dlaczego te dwie bardzo różne taktyki nie sprawdzają się dobrze.
Zamiast próbować używać konstrukcji czasu kompilacji do symulacji czasu wykonywania, przyjrzyjmy się, co
extends
faktycznie robi: zgodnie z przepełnieniem stosu i Wikipedii , rozszerzenie jest używane do wskazywania podklas.C ++ obsługuje również podklasy.
Pokazujesz również klasę kontenera, która używa wymazywania typów w postaci ogólnej i rozszerza się, aby wykonać sprawdzenie typu. W C ++ musisz samodzielnie wykonać maszynę do wymazywania typów, co jest proste: utworzyć wskaźnik do nadklasy.
Umieśćmy to w typedef, aby ułatwić korzystanie z niego, zamiast tworzyć całą klasę, et voila:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
Na przykład:
Wygląda na to, że List jest interfejsem reprezentującym rodzaj kolekcji. Interfejs w C ++ byłby jedynie klasą abstrakcyjną, to znaczy klasą, która implementuje tylko czyste metody wirtualne. Korzystając z tej metody, możesz łatwo zaimplementować przykład Java w C ++, bez żadnych pojęć lub specjalizacji w szablonach. Działałby również tak wolno, jak typy generyczne w stylu Java ze względu na wyszukiwanie wirtualnych tabel, ale często może to być akceptowalna strata.
źródło
Jak wygląda odpowiednik, który akceptuje tylko typy T pochodzące z typu List
źródło
Streszczenie: nie rób tego.
Odpowiedź j_random_hackera mówi, jak to zrobić. Chciałbym jednak również zwrócić uwagę, że nie należy tego robić. Cały sens szablonów polega na tym, że mogą akceptować dowolny zgodny typ, a ograniczenia typu stylu Java to przerywają.
Ograniczenia typu Java to błąd, a nie funkcja. Są tam, ponieważ Java wymazuje typy typów ogólnych, więc Java nie może dowiedzieć się, jak wywołać metody na podstawie samych wartości parametrów typu.
Z drugiej strony C ++ nie ma takiego ograniczenia. Typy parametrów szablonów mogą być dowolnym typem zgodnym z operacjami, z którymi są używane. Nie musi istnieć wspólna klasa bazowa. Jest to podobne do "Duck Typing" w Pythonie, ale wykonywane w czasie kompilacji.
Prosty przykład pokazujący moc szablonów:
Ta funkcja sumująca może sumować wektor dowolnego typu, który obsługuje prawidłowe operacje. Działa zarówno z prymitywami, takimi jak int / long / float / double, jak i typami liczbowymi zdefiniowanymi przez użytkownika, które przeciążają operator + =. Do licha, możesz nawet użyć tej funkcji do łączenia łańcuchów, ponieważ obsługują one + =.
Nie jest konieczne pakowanie / rozpakowywanie prymitywów.
Zauważ, że konstruuje również nowe wystąpienia T przy użyciu T (). Jest to trywialne w C ++ przy użyciu niejawnych interfejsów, ale w rzeczywistości nie jest możliwe w Javie z ograniczeniami typu.
Chociaż szablony C ++ nie mają jawnych ograniczeń typu, nadal są bezpieczne dla typów i nie będą kompilować się z kodem, który nie obsługuje poprawnych operacji.
źródło
Nie jest to możliwe w zwykłym C ++, ale możesz zweryfikować parametry szablonu w czasie kompilacji za pomocą funkcji Concept Checking, np. Używając BCCL firmy Boost .
Od wersji C ++ 20 koncepcje stają się oficjalną cechą języka.
źródło
Upewnij się, że klasy pochodne dziedziczą strukturę FooSecurity, a kompilator będzie zdenerwowany we wszystkich właściwych miejscach.
źródło
Type::FooSecurity
jest używany w klasie szablonu. Jeśli klasa, przekazana w argumencie szablonu, nie maFooSecurity
, próba jej użycia powoduje błąd. Jest pewne, że jeśli klasa przekazana w szablonie nie ma FooSecurity, z której nie pochodziBase
.Użycie koncepcji C ++ 20
https://en.cppreference.com/w/cpp/language/constraints cppreference podaje przypadek użycia dziedziczenia jako wyraźny przykład koncepcji:
Zgaduję, że dla wielu baz składnia będzie wyglądać następująco:
Wydaje się, że GCC 10 zaimplementowało to: https://gcc.gnu.org/gcc-10/changes.html i możesz go pobrać jako PPA na Ubuntu 20.04 . https://godbolt.org/ Mój lokalny GCC 10.1 jeszcze nie rozpoznał
concept
, więc nie jestem pewien, co się dzieje.źródło
Nie.
W zależności od tego, co próbujesz osiągnąć, mogą istnieć odpowiednie (lub nawet lepsze) substytuty.
Przejrzałem trochę kodu STL (w Linuksie myślę, że to ten pochodzący z implementacji SGI). Ma „twierdzenia dotyczące koncepcji”; na przykład, jeśli potrzebujesz typu, który rozumie
*x
i++x
, twierdzenie koncepcyjne zawierałoby ten kod w funkcji nic nie rób (lub coś podobnego). Wymaga to trochę narzutów, więc mądrze może być umieszczenie go w makrze, od którego zależy definicja#ifdef debug
.Jeśli naprawdę chcesz wiedzieć o relacji podklasy, możesz stwierdzić w konstruktorze, że
T instanceof list
(z wyjątkiem tego, że w C ++ jest inaczej pisane). W ten sposób możesz przetestować wyjście z kompilatora, nie będąc w stanie go sprawdzić.źródło
Nie ma słowa kluczowego dla tego typu kontroli, ale możesz umieścić w nim kod, który przynajmniej zawiedzie w uporządkowany sposób:
(1) Jeśli chcesz, aby szablon funkcji akceptował tylko parametry określonej klasy bazowej X, przypisz go do odwołania X w Twojej funkcji. (2) Jeśli chcesz akceptować funkcje, ale nie prymitywy lub odwrotnie, lub chcesz filtrować klasy w inny sposób, wywołaj (pustą) funkcję pomocniczą szablonu w swojej funkcji, która jest zdefiniowana tylko dla klas, które chcesz zaakceptować.
Możesz użyć (1) i (2) również w funkcjach składowych klasy, aby wymusić te sprawdzenia typu na całej klasie.
Prawdopodobnie możesz umieścić to w jakimś inteligentnym Makro, aby złagodzić ból. :)
źródło
Cóż, możesz stworzyć swój szablon, czytając coś takiego:
To jednak spowoduje, że ograniczenie będzie niejawne, a ponadto nie możesz po prostu podać niczego, co wygląda jak lista. Istnieją inne sposoby ograniczenia używanych typów kontenerów, na przykład poprzez wykorzystanie określonych typów iteratorów, które nie istnieją we wszystkich kontenerach, ale znowu jest to bardziej niejawne niż jawne ograniczenie.
O ile mi wiadomo, konstrukcja, która odzwierciedlałaby instrukcję Java w pełnym zakresie, nie istnieje w obecnym standardzie.
Istnieją sposoby na ograniczenie typów, których możesz używać w szablonie, który piszesz, za pomocą określonych czcionek typu w szablonie. Zapewni to, że kompilacja specjalizacji szablonu dla typu, który nie zawiera tej konkretnej definicji typu, nie powiedzie się, więc można selektywnie obsługiwać / nie obsługiwać niektórych typów.
W C ++ 11 wprowadzenie pojęć powinno to ułatwić, ale nie sądzę, że zrobi to dokładnie to, czego byś chciał.
źródło