Meine heutige Frage ist ziemlich einfach: Warum kann der Compiler keine Vorlagenparameter aus Klassenkonstruktoren ableiten, so wie es aus Funktionsparametern möglich ist? Warum konnte beispielsweise der folgende Code nicht gültig sein:
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
}
Wie gesagt, ich verstehe, dass dies nicht gültig ist. Meine Frage ist also, warum nicht? Würde dies größere syntaktische Lücken schaffen? Gibt es eine Instanz, in der man diese Funktionalität nicht möchte (wo das Ableiten eines Typs Probleme verursachen würde)? Ich versuche nur, die Logik zu verstehen, die dahinter steckt, Template-Inferenz für Funktionen zuzulassen, aber nicht für entsprechend konstruierte Klassen.
template<class T> Variable<T> make_Variable(T&& p) {return Variable<T>(std::forward<T>(p));}
Antworten:
Ich denke, es ist nicht gültig, weil der Konstruktor nicht immer der einzige Einstiegspunkt der Klasse ist (ich spreche von Kopierkonstruktor und Operator =). Angenommen, Sie verwenden Ihre Klasse folgendermaßen:
Ich bin mir nicht sicher, ob es für den Parser so offensichtlich wäre, zu wissen, welcher Vorlagentyp die MyClass pm ist.
Ich bin mir nicht sicher, ob das, was ich gesagt habe, Sinn macht, aber ich kann gerne einen Kommentar hinzufügen. Das ist eine interessante Frage.
C ++ 17
Es wird akzeptiert, dass C ++ 17 einen Typabzug von Konstruktorargumenten hat.
Beispiele:
Akzeptiertes Papier .
quelle
MyClass *pm
hier wäre aus demselben Grund ungültig, aus dem eine deklarierte Funktiontemplate <typename T> void foo();
nicht ohne explizite Spezialisierung aufgerufen werden kann.Sie können nicht tun, was Sie aus Gründen fragen, die andere angesprochen haben, aber Sie können dies tun:
was für alle Absichten und Zwecke das gleiche ist, wonach Sie fragen. Wenn Sie die Kapselung lieben, können Sie make_variable zu einer statischen Elementfunktion machen. Das nennen die Leute Konstruktor. Es macht also nicht nur das, was Sie wollen, sondern es wird fast so genannt, wie Sie es wollen: Der Compiler leitet den Template-Parameter vom (benannten) Konstruktor ab.
NB: Jeder vernünftige Compiler optimiert das temporäre Objekt, wenn Sie so etwas schreiben
quelle
auto v = make_variable(instance)
Sie dies tun, so dass Sie den Typ nicht wirklich angeben müssenstatic
Mitglied zu deklarieren ... denken Sie eine Sekunde darüber nach. Abgesehen davon: Freie Make-Funktionen waren in der Tat die Lösung, aber es ist eine Menge redundanter Boilerplate, die Sie während der Eingabe nur wissen sollten, weil der Compiler Zugriff auf alle Informationen hat, die Sie wiederholen. .. und zum Glück kanonisiert C ++ 17 das.Im aufgeklärten Zeitalter des Jahres 2016, mit zwei neuen Standards, seit diese Frage gestellt wurde, und einem neuen gleich um die Ecke, ist es entscheidend zu wissen, dass Compiler, die den C ++ 17-Standard unterstützen , Ihren Code so kompilieren, wie er ist .
Vorlagenargumentabzug für Klassenvorlagen in C ++ 17
Hier (mit freundlicher Genehmigung von Olzhas Zhumabek über die akzeptierte Antwort) befindet sich das Papier, in dem die relevanten Änderungen des Standards aufgeführt sind.
Bedenken aus anderen Antworten ansprechen
Die aktuell am besten bewertete Antwort
Diese Antwort weist darauf hin, dass "Kopierkonstruktor und
operator=
" die richtigen Vorlagenspezialisierungen nicht kennen würden.Dies ist Unsinn, da der Standard-Kopierkonstruktor
operator=
nur für einen bekannten Vorlagentyp existiert:Wie ich in den Kommentaren bemerkt habe, gibt es hier keinen Grund für
MyClass *pm
eine rechtliche Erklärung mit oder ohne die neue Form der Folgerung:MyClass
ist kein Typ (es ist eine Vorlage), daher ist es nicht sinnvoll, einen Zeiger von zu deklarieren TypMyClass
. Hier ist eine Möglichkeit, das Beispiel zu beheben:Hier
pm
ist bereits vom richtigen Typ, und so ist die Folgerung trivial. Darüber hinaus ist es unmöglich, beim Aufruf des Kopierkonstruktors versehentlich Typen zu mischen :Hier
pm
wird ein Zeiger auf eine Kopie von seinm
. HierMyClass
wird eine Kopie erstelltm
, die vom Typ istMyClass<string>
(und nicht vom nicht vorhandenen TypMyClass
). An dem Punkt, an dem auf denpm
Typ geschlossen wird, gibt es also genügend Informationen, um zu wissen, dass der Vorlagentyp vonm
und damit der Vorlagentyp vonpm
iststring
.Darüber hinaus führt Folgendes immer zu einem Kompilierungsfehler :
Dies liegt daran, dass die Deklaration des Kopierkonstruktors nicht als Vorlage dient:
Hier stimmt der Vorlagentyp des Copy-Konstruktor-Arguments mit dem Vorlagentyp der Klasse insgesamt überein . dh wenn
MyClass<string>
instanziiert wird,MyClass<string>::MyClass(const MyClass<string>&);
wird damit instanziiert, und wennMyClass<int>
instanziiert wird,MyClass<int>::MyClass(const MyClass<int>&);
wird instanziiert. Sofern dies nicht explizit angegeben oder ein Konstruktor mit Vorlagen deklariert ist, gibt es für den Compiler keinen Grund zur InstanziierungMyClass<int>::MyClass(const MyClass<string>&);
, was offensichtlich unangemessen wäre.Die Antwort von Cătălin Pitiș
Pitiș gibt ein Beispiel, das ableitet
Variable<int>
undVariable<double>
dann sagt:Wie im vorherigen Beispiel erwähnt, handelt es sich bei sich
Variable
selbst nicht um einen Typnamen, obwohl die neue Funktion ihn syntaktisch wie einen Namen aussehen lässt.Pitiș fragt dann, was passieren würde, wenn kein Konstruktor angegeben wird, der die entsprechende Schlussfolgerung zulässt. Die Antwort ist, dass keine Inferenz zulässig ist, da die Inferenz durch den Konstruktoraufruf ausgelöst wird . Ohne einen Konstruktoraufruf gibt es keine Schlussfolgerung .
Dies ähnelt der Frage, welche Version von
foo
hier abgeleitet wird:Die Antwort ist, dass dieser Code aus dem angegebenen Grund illegal ist.
MSalters Antwort
Dies ist, soweit ich das beurteilen kann, die einzige Antwort, die berechtigte Bedenken hinsichtlich der vorgeschlagenen Funktion aufwirft.
Das Beispiel ist:
Die Schlüsselfrage ist, wählt der Compiler hier den vom Typ abgeleiteten Konstruktor oder den Kopierkonstruktor aus?
Wenn wir den Code ausprobieren, können wir sehen, dass der Kopierkonstruktor ausgewählt ist. So erweitern Sie das Beispiel :
Ich bin mir nicht sicher, wie der Vorschlag und die neue Version des Standards dies spezifizieren. es scheint durch "Abzugsleitfäden" bestimmt zu sein, die ein neues Stück Standard sind, das ich noch nicht verstehe.
Ich bin mir auch nicht sicher, warum der
var4
Abzug illegal ist; Der Compilerfehler von g ++ scheint darauf hinzudeuten, dass die Anweisung als Funktionsdeklaration analysiert wird.quelle
var4
ist nur ein Fall der "ärgerlichsten Analyse" (nicht im Zusammenhang mit dem Abzug von Vorlagenargumenten). Früher haben wir dafür nur zusätzliche Parens verwendet, aber heutzutage denke ich, dass die Verwendung von Zahnspangen zur eindeutigen Kennzeichnung der Konstruktion der übliche Rat ist.Variable var4(Variable(num));
als Funktionsdeklaration behandelt wird? Wenn ja, warum istVariable(num)
eine gültige Parameterspezifikation?Es fehlt noch: Der folgende Code ist nicht eindeutig:
quelle
Angenommen, der Compiler unterstützt Ihre Fragen. Dann ist dieser Code gültig:
Jetzt habe ich den gleichen Typnamen (Variable) im Code für zwei verschiedene Typen (Variable und Variable). Aus meiner subjektiven Sicht beeinträchtigt dies die Lesbarkeit des Codes ziemlich stark. Den gleichen Typnamen für zwei verschiedene Typen im gleichen Namespace zu haben, erscheint mir irreführend.
Späteres Update: Noch etwas zu beachten: teilweise (oder vollständige) Vorlagenspezialisierung.
Was ist, wenn ich mich auf Variable spezialisiere und keinen Konstruktor bereitstelle, wie Sie es erwarten?
Also hätte ich:
Dann habe ich den Code:
Was soll der Compiler tun? Verwenden Sie die generische Variablenklassendefinition, um zu schließen, dass es sich um eine Variable handelt, und stellen Sie dann fest, dass Variable keinen Parameterkonstruktor bereitstellt.
quelle
Der C ++ 03- und der C ++ 11-Standard erlauben keine Ableitung von Vorlagenargumenten von den an den Konstruktor übergebenen Parametern.
Es gibt jedoch einen Vorschlag für die "Ableitung von Vorlagenparametern für Konstruktoren", damit Sie möglicherweise bald das bekommen, wonach Sie fragen. Bearbeiten: Diese Funktion wurde für C ++ 17 bestätigt.
Siehe: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html und http://www.open-std.org/jtc1/sc22/wg21/docs/ Papers / 2015 / p0091r0.html
quelle
Viele Klassen hängen nicht von Konstruktorparametern ab. Es gibt nur wenige Klassen, die nur einen Konstruktor haben und basierend auf den Typen dieses Konstruktors parametrisieren.
Wenn Sie wirklich eine Vorlageninferenz benötigen, verwenden Sie eine Hilfsfunktion:
quelle
Die Ableitung von Typen ist auf Vorlagenfunktionen in aktuellem C ++ beschränkt, aber es ist seit langem bekannt, dass die Ableitung von Typen in anderen Kontexten sehr nützlich wäre. Daher C ++ 0x
auto
.Während genau das , was Sie vorschlagen, in C ++ 0x nicht möglich ist, zeigt das Folgende, dass Sie ziemlich nahe kommen können:
quelle
Sie haben Recht, der Compiler könnte es leicht erraten, aber soweit ich weiß, ist es nicht im Standard oder C ++ 0x, sodass Sie mindestens weitere 10 Jahre warten müssen (ISO-Standards mit fester Umkehrrate), bevor Compiller-Anbieter diese Funktion hinzufügen
quelle
Schauen wir uns das Problem mit Bezug auf eine Klasse an, mit der jeder vertraut sein sollte - std :: vector.
Erstens besteht eine sehr häufige Verwendung von Vektoren darin, den Konstruktor zu verwenden, der keine Parameter akzeptiert:
In diesem Fall kann offensichtlich keine Inferenz durchgeführt werden.
Eine zweite häufige Verwendung ist das Erstellen eines Vektors mit Vorgröße:
Hier, wenn Inferenz verwendet wurde:
Wir erhalten einen Vektor von Ints, keine Strings, und vermutlich ist er nicht dimensioniert!
Betrachten Sie zum Schluss Konstruktoren, die mehrere Parameter annehmen - mit "Inferenz":
Welcher Parameter sollte für die Inferenz verwendet werden? Wir müssten dem Compiler sagen, dass es der zweite sein sollte.
Bei all diesen Problemen für eine Klasse, die so einfach wie ein Vektor ist, ist leicht zu erkennen, warum keine Inferenz verwendet wird.
quelle
Wenn Sie den Ctor zu einer Vorlage machen, kann die Variable nur eine Form, aber verschiedene Ctors haben:
Sehen? Wir können nicht mehrere Variable :: data-Mitglieder haben.
quelle
Siehe Den C ++ Template - Argument Abzug für weitere Informationen zu diesem Thema .
quelle