Moje dzisiejsze pytanie jest dość proste: dlaczego kompilator nie może wywnioskować parametrów szablonu z konstruktorów klas, podobnie jak może to zrobić z parametrów funkcji? Na przykład, dlaczego następujący kod nie mógł być prawidłowy:
template<typename obj>
class Variable {
obj data;
public: Variable(obj d)
{
data = d;
}
};
int main()
{
int num = 2;
Variable var(num); //would be equivalent to Variable<int> var(num),
return 0; //but actually a compile error
}
Jak mówię, rozumiem, że to nie jest poprawne, więc moje pytanie brzmi: dlaczego tak nie jest? Czy pozwolenie na to stworzyłoby jakieś poważne luki składniowe? Czy istnieje przypadek, w którym nie chciałoby się tej funkcjonalności (gdzie wnioskowanie typu spowodowałoby problemy)? Po prostu próbuję zrozumieć logikę stojącą za zezwalaniem na wnioskowanie z szablonu dla funkcji, ale nie dla odpowiednio skonstruowanych klas.
template<class T> Variable<T> make_Variable(T&& p) {return Variable<T>(std::forward<T>(p));}
Odpowiedzi:
Myślę, że nie jest to poprawne, ponieważ konstruktor nie zawsze jest jedynym punktem wejścia klasy (mówię o konstruktorze kopiującym i operatorze =). Załóżmy więc, że używasz swojej klasy w następujący sposób:
Nie jestem pewien, czy analizator składni wiedziałby, jaki typ szablonu to MyClass pm;
Nie jestem pewien, czy to, co powiedziałem, ma sens, ale możesz dodać komentarz, to interesujące pytanie.
C ++ 17
Przyjmuje się, że C ++ 17 będzie miał dedukcję typu z argumentów konstruktora.
Przykłady:
Akceptowany papier .
źródło
MyClass *pm
Tutaj byłoby niepoprawne z tego samego powodu, dla którego zadeklarowana funkcjatemplate <typename T> void foo();
nie może zostać wywołana bez wyraźnej specjalizacji.Nie możesz zrobić tego, o co prosisz, z powodów wskazanych przez inne osoby, ale możesz to zrobić:
który ze wszystkich zamiarów i celów jest tym samym, o co prosisz. Jeśli kochasz hermetyzację, możesz uczynić make_variable statyczną funkcją składową. Tak ludzie nazywają konstruktora. Więc nie tylko robi to, co chcesz, ale nazywa się to prawie tym, czego chcesz: kompilator wnioskuje parametr szablonu z (nazwanego) konstruktora.
Uwaga: każdy rozsądny kompilator zoptymalizuje tymczasowy obiekt, kiedy napiszesz coś takiego jak
źródło
auto v = make_variable(instance)
więc tak naprawdę nie musisz określać typustatic
członka ... pomyśl o tym przez chwilę. Poza tym: darmowe funkcje czynią były rzeczywiście rozwiązanie, ale jest wiele nadmiarowych boilerplate, że podczas pisania to, po prostu wiem, że nie powinno się ponieważ kompilator ma dostęp do wszystkich informacji jesteś powtarzać. .. i na szczęście C ++ 17 to kanonizuje.W oświeconym wieku 2016 roku, gdy mamy dwa nowe standardy od czasu zadania tego pytania i nowy tuż za rogiem, najważniejsze jest to, aby wiedzieć, że kompilatory obsługujące standard C ++ 17 skompilują kod tak, jak jest .
Odliczenie argumentów-szablonów dla szablonów klas w C ++ 17
Tutaj (dzięki uprzejmości redakcji przyjętej odpowiedzi Olzhasa Zhumabka) znajduje się artykuł szczegółowo opisujący odpowiednie zmiany w standardzie.
Rozwiązywanie problemów wynikających z innych odpowiedzi
Aktualnie najwyżej oceniana odpowiedź
Ta odpowiedź wskazuje, że „konstruktor kopiujący i
operator=
” nie znałby prawidłowych specjalizacji szablonów.To nonsens, ponieważ standardowy konstruktor kopiujący
operator=
istnieje tylko dla znanego typu szablonu:Oto, jak zauważył w komentarzach, nie ma żadnego powodu, aby
MyClass *pm
być deklaracja prawny z lub bez nowej formy wnioskowania:MyClass
Nie jest typem (jest to szablon), więc to nie ma sensu, aby zadeklarować wskaźnik z typMyClass
. Oto jeden możliwy sposób naprawienia tego przykładu:Tutaj
pm
jest już właściwego typu, więc wnioskowanie jest banalne. Co więcej, nie można przypadkowo pomieszać typów podczas wywoływania konstruktora kopiującego:Tutaj
pm
będzie wskaźnik do kopiim
. TutajMyClass
jest konstruowana na podstawie kopii -m
która jest typuMyClass<string>
(a nie typu nieistniejącegoMyClass
). Tak więc, w miejscu, gdziepm
„s typ jest wywnioskować, że jest wystarczająca ilość informacji, aby wiedzieć, że szablon-typm
, a zatem szablon-typpm
jeststring
.Co więcej, następujące informacje zawsze powodują błąd kompilacji :
Dzieje się tak, ponieważ deklaracja konstruktora kopiującego nie jest szablonem:
W tym przypadku typ szablonu argumentu kopiuj-konstruktora jest zgodny z typem szablonu całej klasy; tj. kiedy
MyClass<string>
jest tworzony,MyClass<string>::MyClass(const MyClass<string>&);
jest tworzony z nim, a kiedyMyClass<int>
jest tworzony,MyClass<int>::MyClass(const MyClass<int>&);
jest tworzony. O ile nie zostanie to jawnie określone lub nie zostanie zadeklarowany konstruktor oparty na szablonie, nie ma powodu, dla którego kompilatorMyClass<int>::MyClass(const MyClass<string>&);
miałby tworzyć instancję , co oczywiście byłoby niewłaściwe.Odpowiedź Cătălin Pitiș
Pitis podaje przykład wyciągania
Variable<int>
iVariable<double>
, po czym stwierdza:Jak zauważono w poprzednim przykładzie,
Variable
sama w sobie nie jest nazwą typu, mimo że nowa funkcja sprawia, że wygląda jak nazwa.Pitiș pyta następnie, co by się stało, gdyby nie podano konstruktora, który pozwoliłby na odpowiednie wnioskowanie. Odpowiedź brzmi, że żadne wnioskowanie nie jest dozwolone, ponieważ jest ono wyzwalane przez wywołanie konstruktora . Bez wywołania konstruktora nie ma wnioskowania .
Jest to podobne do pytania o wersję
foo
wydedukowaną tutaj:Odpowiedź brzmi, że ten kod jest nielegalny z podanego powodu.
Odpowiedź MSaltera
O ile mi wiadomo, jest to jedyna odpowiedź, która może wzbudzić uzasadnione obawy dotyczące proponowanej funkcji.
Oto przykład:
Kluczowe pytanie brzmi: czy kompilator wybiera tutaj konstruktor wywodzący się z typu , czy konstruktor kopiujący ?
Wypróbowując kod, widzimy, że wybrany został konstruktor kopiujący. Aby rozwinąć przykład :
Nie jestem pewien, jak to określa propozycja i nowa wersja normy; wydaje się, że określają go „przewodniki po dedukcji”, które są nowym kawałkiem standardu, którego jeszcze nie rozumiem.
Nie jestem też pewien, dlaczego
var4
odliczenie jest nielegalne; błąd kompilatora z g ++ wydaje się wskazywać, że instrukcja jest analizowana jako deklaracja funkcji.źródło
var4
jest tylko przypadkiem „najbardziej irytującej analizy” (niezwiązanej z dedukcją argumentów szablonu). Kiedyś używaliśmy do tego po prostu dodatkowych parenów, ale obecnie myślę, że używanie nawiasów klamrowych do jednoznacznego oznaczenia konstrukcji jest zwykłą radą.Variable var4(Variable(num));
jest to traktowane jako deklaracja funkcji? Jeśli tak, dlaczego jestVariable(num)
to prawidłowa specyfikacja parametru?Wciąż brakuje: sprawia, że następujący kod jest dość niejednoznaczny:
źródło
Przypuśćmy, że kompilator obsługuje to, o co prosiłeś. Wtedy ten kod jest ważny:
Teraz mam tę samą nazwę typu (zmienna) w kodzie dla dwóch różnych typów (zmienna i zmienna). Z mojego subiektywnego punktu widzenia ma to duży wpływ na czytelność kodu. Posiadanie tej samej nazwy typu dla dwóch różnych typów w tej samej przestrzeni nazw wydaje mi się mylące.
Późniejsza aktualizacja: Kolejna rzecz do rozważenia: częściowa (lub pełna) specjalizacja szablonów.
A co, jeśli specjalizuję się w zmiennej i nie dostarczam konstruktora takiego, jakiego oczekujesz?
Więc miałbym:
Następnie mam kod:
Co powinien zrobić kompilator? Użyj ogólnej definicji klasy Variable, aby wywnioskować, że jest to zmienna, a następnie przekonaj się, że zmienna nie zapewnia jednego konstruktora parametrów?
źródło
Standardy C ++ 03 i C ++ 11 nie pozwalają na wyprowadzanie argumentów szablonu z parametrów przekazanych do konstruktora.
Ale jest propozycja "Odliczenie parametrów szablonu dla konstruktorów", więc możesz otrzymać to, o co prosisz wkrótce. Edycja: rzeczywiście, ta funkcja została potwierdzona dla C ++ 17.
Zobacz: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html i http://www.open-std.org/jtc1/sc22/wg21/docs/ dokumenty / 2015 / p0091r0.html
źródło
Wiele klas nie zależy od parametrów konstruktora. Istnieje tylko kilka klas, które mają tylko jeden konstruktor i parametryzują na podstawie typów tego konstruktora.
Jeśli naprawdę potrzebujesz wnioskowania z szablonu, użyj funkcji pomocniczej:
źródło
Odliczanie typów jest ograniczone do funkcji szablonów w obecnym C ++, ale od dawna zdawano sobie sprawę, że odejmowanie typów w innych kontekstach byłoby bardzo przydatne. Stąd C ++ 0x
auto
.Chociaż dokładnie to , co sugerujesz, nie będzie możliwe w C ++ 0x, poniższe pokazują, że możesz być całkiem blisko:
źródło
Masz rację, kompilator mógł z łatwością odgadnąć, ale o ile wiem, nie ma go w standardzie ani w C ++ 0x, więc będziesz musiał poczekać co najmniej 10 lat (stała szybkość zmian według standardów ISO), zanim dostawcy kompilatorów dodają tę funkcję
źródło
Przyjrzyjmy się problemowi w odniesieniu do klasy, którą każdy powinien znać - std :: vector.
Po pierwsze, bardzo częstym zastosowaniem wektora jest użycie konstruktora, który nie przyjmuje parametrów:
W tym przypadku oczywiście nie można wnioskować.
Drugim powszechnym zastosowaniem jest utworzenie wektora o ustalonej wielkości:
Tutaj, jeśli zastosowano wnioskowanie:
otrzymujemy wektor int, a nie stringów, i przypuszczalnie nie ma rozmiaru!
Na koniec rozważ konstruktory, które pobierają wiele parametrów - z „wnioskiem”:
Którego parametru należy użyć do wnioskowania? Musielibyśmy w jakiś sposób powiedzieć kompilatorowi, że powinien to być ten drugi.
Biorąc pod uwagę wszystkie te problemy dla klasy tak prostej jak wektor, łatwo zrozumieć, dlaczego nie używa się wnioskowania.
źródło
Uczynienie z ctora szablonu Zmienna może mieć tylko jedną formę, ale różne ctory:
Widzieć? Nie możemy mieć wielu członków Variable :: data.
źródło
Aby uzyskać więcej informacji na ten temat, zobacz The C ++ Template Argument Deduction .
źródło