Pourquoi ne pas déduire le paramètre template du constructeur?

Ma question aujourd’hui est assez simple: pourquoi le compilateur ne peut-il pas déduire des parameters de modèle à partir des constructeurs de classes, comme il peut le faire avec les parameters de fonction? Par exemple, pourquoi le code suivant ne pourrait-il pas être valide:

template class Variable { obj data; public: Variable(obj d) { data = d; } }; int main() { int num = 2; Variable var(num); //would be equivalent to Variable var(num), return 0; //but actually a comstack error } 

Comme je l’ai dit, je comprends que ce n’est pas valable, alors ma question est pourquoi n’est-ce pas? Cela permettrait-il de créer des trous syntaxiques majeurs? Y a-t-il une instance où l’on ne voudrait pas cette fonctionnalité (où l’inférence d’un type causerait des problèmes)? J’essaie juste de comprendre la logique qui sous-tend l’autorisation de modèle pour les fonctions, mais pas pour les classes construites de manière appropriée.

Je pense que ce n’est pas valable parce que le constructeur n’est pas toujours le seul point d’entrée de la classe (je parle du constructeur et de l’opérateur de copie =). Alors, supposons que vous utilisiez votre classe comme ceci:

 MyClass m(ssortingng s); MyClass *pm; *pm = m; 

Je ne suis pas sûr qu’il soit si évident pour l’parsingur de savoir quel type de modèle est le MyClass pm;

Je ne sais pas si ce que j’ai dit a du sens, mais n’hésitez pas à append des commentaires, c’est une question intéressante.

C ++ 17

Il est admis que C ++ 17 aura une déduction de type des arguments de constructeur.

Exemples:

 std::pair p(2, 4.5); std::tuple t(4, 3, 2.5); 

Papier accepté

Vous ne pouvez pas faire ce que vous demandez pour des raisons que d’autres personnes ont abordées, mais vous pouvez le faire:

 template class Variable { public: Variable(T d) {} }; template Variable make_variable(T instance) { return Variable(instance); } 

qui pour toutes les intentions et les fins est la même chose que vous demandez. Si vous aimez l’encapsulation, vous pouvez rendre make_variable une fonction membre statique. C’est ce que les gens appellent le constructeur nommé. Donc, non seulement il fait ce que vous voulez, mais il s’appelle presque ce que vous voulez: le compilateur infère le paramètre template du constructeur (nommé).

NB: tout compilateur raisonnable optimisera l’object temporaire lorsque vous écrivez quelque chose comme

 Variable v = make_variable(instance); 

À l’ère éclairée de 2016, avec deux nouvelles normes à notre actif depuis que cette question a été posée et une nouvelle à venir, la chose cruciale à savoir est que les compilateurs supportant la norme C ++ 17 comstackront votre code tel quel. .

Déduction des arguments de modèle pour les modèles de classes en C ++ 17

Ici (avec l’aimable autorisation de Olzhas Zhumabek de la réponse acceptée) se trouve le document détaillant les changements pertinents apportés à la norme.

Répondre aux préoccupations d’autres réponses

La réponse actuelle la mieux notée

Cette réponse indique que “copy constructeur et operator= ” ne connaîtraient pas les spécialisations de modèles correctes.

Cela n’a aucun sens, car le constructeur de copie et l’ operator= n’existent que pour un type de modèle connu :

 template  class MyClass { MyClass(const MyClass&) =default; ... etc... }; // usage example modified from the answer MyClass m(ssortingng("blah blah blah")); MyClass *pm; // WHAT IS THIS? *pm = m; 

Ici, comme je l’ai noté dans les commentaires, il n’ya aucune raison pour que MyClass *pm soit une déclaration légale avec ou sans la nouvelle forme d’inférence: MyClass n’est pas un type (c’est un modèle), donc cela n’a aucun sens de déclarer un pointeur de type MyClass . Voici un moyen possible de corriger l’exemple:

 MyClassssortingng m("blah blah blah")); decltype(m) *pm; // uses type inference! *pm = m; 

Ici, pm est déjà du type correct, et donc l’inférence est sortingviale. De plus, il est impossible de mélanger accidentellement les types lors de l’appel du constructeur de copie:

 MyClass m(ssortingng("blah blah blah")); auto pm = &(MyClass(m)); 

Ici, pm sera un pointeur sur une copie de m . Ici, MyClass est en train d’être copié à partir de m qui est du type MyClass (et non du type inexistant MyClass ). Ainsi, au point où le type de pm est déduit, il existe suffisamment d’informations pour savoir que le type de modèle de m , et donc le type de modèle de pm , est une ssortingng .

De plus, ce qui suit provoquera toujours une erreur de compilation :

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

En effet, la déclaration du constructeur de la copie n’est pas basée sur un modèle:

 MyClass(const MyClass&); 

Ici, l’argument de type copy-constructeur correspond au type de modèle de la classe en général; c’est-à-dire lorsque MyClass est instancié, MyClass::MyClass(const MyClass&); est instancié avec celui-ci et lorsque MyClass est instancié, MyClass::MyClass(const MyClass&); est instancié. À moins que cela ne soit explicitement spécifié ou qu’un constructeur basé sur des modèles soit déclaré, le compilateur n’a aucune raison d’instancier MyClass::MyClass(const MyClass&); , ce qui serait évidemment inapproprié.

La réponse de Cătălin Pitiș

Pitiș donne un exemple déduisant la Variable et la Variable , puis indique:

J’ai le même nom de type (Variable) dans le code pour deux types différents (Variable et Variable). De mon sharepoint vue subjectif, cela affecte beaucoup la lisibilité du code.

Comme indiqué dans l’exemple précédent, la Variable elle-même n’est pas un nom de type, même si la nouvelle fonctionnalité le fait ressembler à une syntaxe.

Pitiș demande ensuite ce qui se passerait si aucun constructeur n’était donné qui permettrait l’inférence appropriée. La réponse est qu’aucune inférence n’est autorisée, car l’inférence est déclenchée par l’ appel du constructeur . Sans appel constructeur, il n’y a pas d’inférence .

Ceci est similaire à demander quelle version de foo est déduite ici:

 template  foo(); foo(); 

La réponse est que ce code est illégal, pour la raison indiquée.

Réponse de MSalter

C’est, pour autant que je sache, la seule réponse à apporter à une préoccupation légitime concernant la fonctionnalité proposée.

L’exemple est:

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

La question clé est la suivante: le compilateur sélectionne-t -il le constructeur de type inféré ici ou le constructeur de copie ?

En essayant le code, nous pouvons voir que le constructeur de la copie est sélectionné. Pour développer l’exemple :

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

Je ne sais pas comment la proposition et la nouvelle version de la norme le spécifient; il semble être déterminé par des “guides de déduction”, qui sont un nouveau standard que je ne comprends pas encore.

Je ne sais pas non plus pourquoi la déduction var4 est illégale; l’erreur du compilateur de g ++ semble indiquer que l’instruction est analysée en tant que déclaration de fonction.

Toujours manquant: Cela rend le code suivant assez ambigu:

 int main() { int num = 2; Variable var(num); // If equivalent to Variable var(num), Variable var2(var); //Variable or Variable> ? } 

En supposant que le compilateur supporte ce que vous avez demandé. Alors ce code est valide:

 Variable v1( 10); // Variable // Some code here Variable v2( 20.4); // Variable 

Maintenant, j’ai le même nom de type (Variable) dans le code pour deux types différents (Variable et Variable). De mon sharepoint vue subjectif, cela affecte beaucoup la lisibilité du code. Avoir le même nom de type pour deux types différents dans le même espace de noms me semble trompeur.

Mise à jour ultérieure: Une autre chose à prendre en compte: la spécialisation partielle (ou complète) des modèles.

Que se passe-t-il si je spécialise la variable et ne fournit aucun constructeur comme vous le souhaitez?

Donc j’aurais:

 template<> class Variable { // Provide default constructor only. }; 

Alors j’ai le code:

 Variable v( 10); 

Que doit faire le compilateur? Utilisez la définition générique de la classe Variable pour en déduire qu’il s’agit d’une variable, puis découvrez que la variable ne fournit pas de constructeur de paramètre?

La norme C ++ 03 et la norme C ++ 11 n’autorisent pas la déduction des arguments de modèle à partir des parameters transmis au consturateur.

Mais il existe une proposition pour la “déduction des parameters de modèle pour les constructeurs” afin que vous puissiez obtenir rapidement ce que vous demandez. Edit: en effet, cette fonctionnalité a été confirmée pour C ++ 17.

Voir: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html et http://www.open-std.org/jtc1/sc22/wg21/docs/ articles / 2015 / p0091r0.html

Beaucoup de classes ne dépendent pas des parameters du constructeur. Il n’y a que quelques classes qui ont un seul constructeur, et paramètrent en fonction du ou des types de ce constructeur.

Si vous avez vraiment besoin d’inférence de modèle, utilisez une fonction d’assistance:

 template class Variable { obj data; public: Variable(obj d) : data(d) { } }; template inline Variable makeVariable(const obj& d) { return Variable(d); } 

La déduction de types est limitée aux fonctions de gabarit dans le C ++ actuel, mais on a longtemps compris que la déduction de type dans d’autres contextes serait très utile. D’où l’ auto de C ++ 0x.

Alors que ce que vous proposez ne sera pas possible en C ++ 0x, les exemples suivants vous seront très utiles:

 template  Variable::type> MakeVariable(X&& x) { // remove reference required for the case that x is an lvalue return Variable::type>(std::forward(x)); } void test() { auto v = MakeVariable(2); // v is of type Variable } 

Vous avez raison le compilateur pourrait facilement deviner, mais ce n’est pas dans la norme ou C ++ 0x pour autant que je sache, vous devrez donc attendre au moins 10 ans (taux de rotation fixe des normes ISO) avant que les fournisseurs compilateurs ajoutent cette fonctionnalité

Regardons le problème avec une référence à une classe que tout le monde devrait connaître – std :: vector.

Tout d’abord, une utilisation très courante de vector consiste à utiliser le constructeur sans paramètre:

 vector  v; 

Dans ce cas, aucune inférence ne peut évidemment être effectuée.

Un deuxième usage courant consiste à créer un vecteur prédéfini:

 vector  v(100); 

Ici, si l’inférence était utilisée:

 vector v(100); 

nous obtenons un vecteur d’ints, pas de chaînes, et probablement pas de taille!

Enfin, considérez les constructeurs qui prennent plusieurs parameters – avec “inférence”:

 vector v( 100, foobar() ); // foobar is some class 

Quel paramètre doit être utilisé pour l’inférence? Nous aurions besoin d’un moyen de dire au compilateur qu’il devrait s’agir du second.

Avec tous ces problèmes pour une classe aussi simple que le vecteur, il est facile de voir pourquoi l’inférence n’est pas utilisée.

Faire du modèle un modèle, la variable ne peut avoir qu’une seule forme, mais plusieurs facteurs:

 class Variable { obj data; // let the comstackr guess public: template 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; } 

Voir? Nous ne pouvons pas avoir plusieurs membres Variable :: data.

Voir la déduction de l’argument du modèle C ++ pour plus d’informations à ce sujet.