Dlaczego nie wywnioskować parametru szablonu z konstruktora?
Moje dzisiejsze pytanie jest dość proste: dlaczego kompilator nie może wywnioskować parametrów szablonu z konstruktorów klas, tak samo jak z parametrów funkcji? Na przykład, dlaczego poniższy kod nie może być poprawny:
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 ważne, więc moje pytanie brzmi Dlaczego nie jest? Czy pozwalając na to stworzyć jakieś duże dziury składniowe? Czy istnieje przypadek, w którym nie chcemy tej funkcjonalności (gdzie wnioskowanie typu powodowałoby problemy)? Ja tylko próbuje zrozumieć logikę pozwalającą wnioskować szablonami dla funkcji, ale nie dla odpowiednio skonstruowanych klas.
12 answers
Myślę, że nie jest to poprawne, ponieważ konstruktor nie zawsze jest jedynym punktem wejścia do klasy (mówię o konstruktorze kopiującym i operatorze=). Więc załóżmy, że używasz swojej klasy w ten sposób:
MyClass m(string s);
MyClass *pm;
*pm = m;
Nie jestem pewien, czy byłoby tak oczywiste, aby parser wiedział, jaki typ szablonu jest MyClass pm;
Nie wiem, czy to, co powiedziałem ma sens, ale nie krępuj się dodać komentarz, to ciekawe pytanie.
C++ 17
Przyjmuje się, że C++17 będzie ma odejmowanie typu od argumentów konstruktora.
Przykłady:
std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-28 11:02:58
Nie możesz robić tego, o co prosisz, z powodów, do których zwracają się inni, ale możesz to zrobić:
template<typename T>
class Variable {
public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
return Variable<T>(instance);
}
Który dla wszystkich intencji i celów jest tym samym, o co prosisz. Jeśli kochasz enkapsulację, możesz uczynić make_variable statyczną funkcją członkowską. Tak ludzie nazywają konstruktora. Więc nie tylko robi to, co chcesz, ale prawie nazywa się to, co chcesz: kompilator wyprowadza parametr szablonu z konstruktora (nazwanego).
NB: każdy rozsądny kompilator będzie optimize away the temporary object when you write something like
Variable<T> v = make_variable(instance);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-25 01:26:04
W oświeconej erze 2016 roku, z dwoma nowymi standardami pod naszym pasem, ponieważ to pytanie zostało zadane i Nowy tuż za rogiem, kluczową rzeczą do wiedzenia jest to, że Kompilatory wspierające standard C++17 będą skompilować kod tak, jak jest.
Template-argument dedukcji dla szablonów klas w C++17
Tutaj (Dzięki uprzejmości redakcji Olzhasa Zhumabeka zaakceptowanej odpowiedzi) znajduje się artykuł opisujący istotne zmiany w standard.
Rozwiązywanie problemów z innych odpowiedzi
Aktualna najwyżej oceniana odpowiedź
Ta odpowiedź wskazuje, że "Konstruktor kopiujący i operator=
" nie zna prawidłowych specjalizacji szablonów.
To nonsens, ponieważ standardowy Konstruktor kopiujący i operator=
istnieje tylko {[54] } dla znanego typu szablonu:
template <typename T>
class MyClass {
MyClass(const MyClass&) =default;
... etc...
};
// usage example modified from the answer
MyClass m(string("blah blah blah"));
MyClass *pm; // WHAT IS THIS?
*pm = m;
Tutaj, jak zauważyłam w komentarzach, nie ma żadnego powodu aby MyClass *pm
być deklaracją prawną z lub bez nowej formy wnioskowania: MyClass
nie jest typem (jest to szablon), więc nie ma sensu deklarować wskaźnika typu MyClass
. Oto jeden z możliwych sposobów na poprawienie przykładu:
MyClassstring m("blah blah blah"));
decltype(m) *pm; // uses type inference!
*pm = m;
Tutaj pm
jest już właściwego typu, więc wnioskowanie jest trywialne. Co więcej, nie można przypadkowo mieszać typów podczas wywoływania konstruktora kopiującego:
MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));
Tutaj, {[13] } będzie wskaźnikiem do kopii m
. Tutaj MyClass
jest Kopia-skonstruowana z m
- który jest typu MyClass<string>
(i nie nieistniejącego typu MyClass
). Tak więc w punkcie, w którym Typ pm
jest wnioskowany, jest wystarczającą informacją, aby wiedzieć, że typ szablonu m
, a zatem typ szablonu pm
, jest string
.
Ponadto następujące będą zawsze nie jest to jednak możliwe.]}
To dlatego, że deklaracja konstruktora kopiującego to , a nie szablon: Tutaj, argument copy-constructor ' s template-type pasuje do szablonu klasy; tzn. gdy Pitiș podaje przykład dedukcji Mam tę samą nazwę typu (zmienna) w kodzie dla dwóch różnych typów (zmienna i zmienna). Z mojego subiektywnego punktu widzenia wpływa to w dużym stopniu na czytelność kodu. Jak wspomniano w poprzednim przykładzie, Pitiș wtedy pyta, co by zdarza się, jeśli nie podano konstruktora, który pozwoliłby na odpowiednie wnioskowanie. Odpowiedź jest taka, że wnioskowanie nie jest dozwolone, ponieważ wnioskowanie jest wywoływane przez wywołanie konstruktora . Bez wywołania konstruktora nie ma wnioskowania . Jest to podobne do pytania, która wersja Odpowiedź jest taka, że ten kod jest nielegalny, z podanego powodu. To jest, o ile mogę powiedzieć, jedyna odpowiedź, która budzi uzasadnione obawy dotyczące proponowanej funkcji. Przykład: Kluczowe pytanie brzmi, czy kompilator wybiera konstruktor Typ-wywnioskowany tutaj czy konstruktor copy? Testując kod, widzimy, że Konstruktor kopiujący jest wybrany. aby rozwinąć na przykładzie : Nie jestem pewien, jak propozycja i nowa wersja standardu to określają; wydaje się, że ustalane przez "przewodniki dedukcyjne", które są nową częścią standardu, którego jeszcze nie rozumiem. Nie jestem również pewien, dlaczego dedukcja MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;
MyClass(const MyClass&);
MyClass<string>
jest instancją, MyClass<string>::MyClass(const MyClass<string>&);
jest instancją z nią, a gdy MyClass<int>
jest instancją, MyClass<int>::MyClass(const MyClass<int>&);
jest instancją. Jeśli nie jest to jawnie określone lub nie jest zadeklarowany konstruktor szablonowy, nie ma powodu, aby kompilator tworzył instancję MyClass<int>::MyClass(const MyClass<string>&);
, co oczywiście byłoby niewłaściwe.Variable<int>
i Variable<double>
, następnie stwierdza:
Variable
sama w sobie jest , a nie nazwą typu, mimo że nowa funkcja sprawia, że wygląda jak jedna składniowo.foo
jest wydedukowana tutaj: template <typename T> foo();
foo();
Odpowiedź Msaltera
Variable var(num); // If equivalent to Variable<int> var(num),
Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
Variable var(num); // infering ctor
Variable var2(var); // copy ctor
Variable var3(move(var)); // move ctor
// Variable var4(Variable(num)); // compiler error
var4
jest nielegalna; błąd kompilatora z g++ zdaje się wskazywać, że instrukcja jest przetwarzana jako deklaracja funkcji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-10 16:47:00
Nadal brakuje: sprawia, że następujący kod jest dość niejednoznaczny:
int main()
{
int num = 2;
Variable var(num); // If equivalent to Variable<int> var(num),
Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-12 15:01:52
Przypuśćmy, że kompilator obsługuje to, o co prosiłeś. Wtedy ten kod jest poprawny:
Variable v1( 10); // Variable<int>
// Some code here
Variable v2( 20.4); // Variable<double>
Teraz mam tą samą nazwę typu (zmienna) w kodzie dla dwóch różnych typów (zmienna i zmienna). Z mojego subiektywnego punktu widzenia wpływa to w dużym stopniu 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ęściowy (lub pełny) szablon specjalizacja.
Co jeśli specjalizuję zmienną i nie dostarczam żadnego konstruktora, jakiego oczekujesz?
Więc ja bym miał:
template<>
class Variable<int>
{
// Provide default constructor only.
};
Wtedy mam kod:
Variable v( 10);
Co powinien zrobić kompilator? Użyj ogólnej definicji klasy zmiennej, aby wywnioskować, że jest zmienna, a następnie odkryć, że zmienna nie dostarcza konstruktora jednego parametru?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-12 07:50:40
Standard C++03 i C++11 nie pozwala na odliczenie argumentów szablonu od parametrów przekazywanych do konsturuktora.
Ale jest propozycja "odliczenia parametru szablonu dla konstruktorów", więc może wkrótce dostaniesz to, o co prosisz. Edit: 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/papers/2015/p0091r0.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-22 23:44:39
Wiele klas nie zależy od parametrów konstruktora. Istnieje tylko kilka klas, które mają tylko jeden konstruktor i są parametryzowane na podstawie typu (typów) tego konstruktora.
Jeśli naprawdę potrzebujesz wnioskowania szablonu, użyj funkcji pomocniczej:
template<typename obj>
class Variable
{
obj data;
public:
Variable(obj d)
: data(d)
{ }
};
template<typename obj>
inline Variable<obj> makeVariable(const obj& d)
{
return Variable<obj>(d);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-12 01:46:50
Dedukcja typów jest ograniczona do funkcji szablonowych w obecnym C++, ale od dawna zdano sobie sprawę, że dedukcja typów w innych kontekstach byłaby bardzo przydatna. Stąd C++0x auto
.
Podczas gdy dokładnie to, co sugerujesz, nie będzie możliwe w C++0x, poniżej widać, że możesz być blisko:
template <class X>
Variable<typename std::remove_reference<X>::type> MakeVariable(X&& x)
{
// remove reference required for the case that x is an lvalue
return Variable<typename std::remove_reference<X>::type>(std::forward(x));
}
void test()
{
auto v = MakeVariable(2); // v is of type Variable<int>
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-12 11:36:09
Masz rację kompilator może się łatwo domyślić, ale nie jest w Standardzie lub C++0x z tego co wiem, więc będziesz musiał poczekać co najmniej 10 lat więcej (ISO standards fixed turn around rate) zanim dostawcy kompilatora dodadzą tę funkcję
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-12 00:01:48
Przyjrzyjmy się problemowi w odniesieniu do klasy, z którą każdy powinien być zaznajomiony z - std:: vector.
Po pierwsze, bardzo powszechnym zastosowaniem wektora jest użycie konstruktora, który nie przyjmuje parametrów:
vector <int> v;
W tym przypadku oczywiście nie można wnioskować.
Drugim powszechnym zastosowaniem jest tworzenie wektora pre-Size:
vector <string> v(100);
Tutaj, jeśli użyto wnioskowania:
vector v(100);
Otrzymujemy wektor intów, a nie ciągów, i prawdopodobnie nie jest on wielkości!
Wreszcie, rozważmy konstruktory, które przyjmują wiele parametrów-z "wnioskowaniem":
vector v( 100, foobar() ); // foobar is some class
Który parametr powinien być użyty do wnioskowania? Potrzebowalibyśmy jakiegoś sposobu na powiedzenie kompilatorowi, że powinien to być drugi.
Z tymi wszystkimi problemami dla klasy tak prostej jak vector, łatwo zrozumieć, dlaczego wnioskowanie nie jest używane.
Czyniąc ctor szablonem zmienna może mieć tylko jeden formularz ale różne ctor:
class Variable {
obj data; // let the compiler guess
public:
template<typename obj>
Variable(obj d)
{
data = d;
}
};
int main()
{
int num = 2;
Variable var(num); // Variable::data int?
float num2 = 2.0f;
Variable var2(num2); // Variable::data float?
return 0;
}
Widzisz? Nie możemy mieć wielu zmiennych:: Data członków.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-12 00:12:30
Zobacz Argument szablonu C++ aby uzyskać więcej informacji na ten temat.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-12 00:14:22