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.

Author: Trevor Hickey, 2009-06-12

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);

Przyjęty papier .

 41
Author: Drahakar,
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);
 22
Author: Lionel,
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.]}

MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;

To dlatego, że deklaracja konstruktora kopiującego to , a nie szablon:

MyClass(const MyClass&);

Tutaj, argument copy-constructor ' s template-type pasuje do szablonu klasy; tzn. gdy 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.

[48] odpowiedź przez Cătălin Pitiș

Pitiș podaje przykład dedukcji Variable<int> i Variable<double>, następnie stwierdza:

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, Variable sama w sobie jest , a nie nazwą typu, mimo że nowa funkcja sprawia, że wygląda jak jedna składniowo.

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 foo jest wydedukowana tutaj:

template <typename T> foo();
foo();

Odpowiedź jest taka, że ten kod jest nielegalny, z podanego powodu.

Odpowiedź Msaltera

To jest, o ile mogę powiedzieć, jedyna odpowiedź, która budzi uzasadnione obawy dotyczące proponowanej funkcji.

Przykład:

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); //Variable<int> or Variable<Variable<int>> ?

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 :

Variable var(num);          // infering ctor
Variable var2(var);         // copy ctor
Variable var3(move(var));   // move ctor
// Variable var4(Variable(num));     // compiler error

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 var4 jest nielegalna; błąd kompilatora z g++ zdaje się wskazywać, że instrukcja jest przetwarzana jako deklaracja funkcji.

 16
Author: Kyle Strand,
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>> ?
}
 11
Author: MSalters,
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?

 9
Author: Cătălin Pitiș,
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

 6
Author: ChetS,
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);
}
 2
Author: rlbond,
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>
}
 1
Author: James Hopkin,
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ę

 0
Author: Robert Gould,
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.

 -1
Author: ,
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 09:19:22

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.
 -2
Author: Nick Dandoulakis,
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.

 -2
Author: Igor Krivokon,
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