Étant donné une fonction de modèle déclarée comme ceci:
template<class T>
int Function(T object);
Un utilisateur peut appeler cette fonction en spécifiant le type de modèle, comme ceci:
int result = Function<float>(100.f); // Valid
Mais la spécification de type est facultative, car le compilateur peut déduire le type de T du type de l'argument fourni; comme ça:
int result = Function(100.f); // Also valid, the compiler deduced the type "float" from the literal's type
Disons que je deviens un peu plus compliqué et que je veux un paramètre de valeur basé sur un modèle comme ceci:
template<class T, T* object>
int Function();
Je peux appeler ma fonction de cette façon:
static float val = 100.f;
// ...
int result = Function<float, &val>();
Ma question est: existe-t-il un moyen de contraindre le compilateur à déduire le type T en fonction du type de l'argument & val?
J'ai besoin d'un moyen de rendre le code suivant valide:
static float val = 100.f;
// ...
int result = Function<&val>();
Peut-on le faire?
En C++ 17, vous pouvez avoir auto
des paramètres de modèle non-type. Cela vous permettra de résoudre votre problème.
Quelque chose comme:
template<auto object, class T=std::decay_t<decltype(*object)>>
int Function();
(en supposant que vous vouliez le type T
dans le corps de Function
)
En C++ 14, la fonctionnalité C++ 17 est manquante. Il a été ajouté exactement parce qu'il manquait. Les solutions de contournement impliquent des macros comme #define UGLY_HACK(...) decltype(__VA_ARGS__), __VA_ARGS__
.
Remarque: la réponse ici a été empruntée au C++ moderne efficace avec quelques (très) ajouts à moi
C'est une de ces questions faciles à poser mais difficiles à répondre! Je me souviens avoir lu un ch entier. après la déduction du type de modèle, et pour un lecteur débutant, la réponse n'est pas claire non plus en une seule lecture. Néanmoins, je vais essayer de le clarifier ici.
Il convient de noter qu'il existe quelque chose appelé Références universelles (qui ne sont pas les mêmes que les références ou les références de valeur r) qui influence la déduction de type de modèle, et je suppose que les lecteurs connaissent les références de valeur l et de valeur r.
Toute définition de modèle de fonction omniprésente ressemble à ceci:
template <typename T>
returnType function(paramType param);
Un appel à la fonction ressemblerait en quelque sorte à ceci:
function(expression);
Le compilateur utilise expression pour déterminer le type de T et le type de paramType. En effet, le plus souvent paramType contient des décorations comme const, const &, const &&, etc. Les débutants seraient tentés croire que le type T déduit par le compilateur sera le même que le type expression, c'est-à-dire l'argument passé à la fonction, mais ce n'est pas toujours le cas. La déduction de type T dépend à la fois de expression et paramType. Selon le paramètre de fonction paramType, il y a trois cas à considérer pour la déduction de type de modèle:
Jetons un coup d'œil à chaque cas un par un
Appelez-moi fou, mais c'est le cas le plus simple qui puisse être rencontré. Dans ce cas, la déduction de type fonctionne comme ceci: (i) Si expression est une référence, ignorez la partie de référence ( ii) puis faire correspondre le modèle de l'expression avec paramType pour déterminer [~ # ~] t [~ # ~]
Voyons un exemple:
template <typename T>
returnType function(T ¶m);
Nous avons les déclarations de variables suivantes:
int x = 23; // x is int
const int const_x = x; // const_x is const int
const int& ref_x = x; // ref_x is a reference to x as const int
L'appel déduit pour [~ # ~] t [~ # ~] et param dans divers appels sont les suivants:
f(x); //T is int, param's type is int&
f(const_x); //T is const int, param's type is const int&
f(ref_x); //T is const int, param's type is const int&
Il y a deux points à noter ici:
(i) le compilateur ignore ici la référence-ness pour la déduction de type
(ii) la const-ness devient une partie de type [~ # ~] t [~ # ~] lors du passage un objet const ou une référence à un objet const, et donc passer des objets const ou des références à un objet const à des fonctions prenant le paramètre T & est sécurisé.
Si nous changeons le paramètre de fonction de T & à const T & , car dans ce cas, nous supposons param comme référence à const , le const - il n'est pas nécessaire être déduit dans le cadre de [~ # ~] t [~ # ~] . Voici un exemple:
template <typename T>
returnType function(const T& param); // param is now a ref-to-const
int x = 23; // same as previous
const int const_x = x; // same as previous
const int& ref_x = x; // same as previous
f(x); // T is int, paramType is const int&
f(const_x); // T is int, paramType is const int&
f(ref_x); // T is int, paramType is const int&
Note: la variable 'x' n'est pas un argument const à 'f ()' mais elle est jusqu'à déduite comme paramètre const
Si paramType est un pointeur, les choses fonctionneront fondamentalement de la même manière qu'avec les références. Il y aura des pointeurs au lieu de références. Par exemple, par souci d'exhaustivité est fourni:
template <typename T>
returnType function( T* paramType); // paramType is now a pointer
int x = 23; // same as before
const int *pointer_x = &x; // pointer_x is pointer to x as const int
f(&x); // T is int, paramType is int*
f(pointer_x); // T is const int, paramType is const int*
Par souci d'exhaustivité, je peux tout aussi bien poster le cas si paramType était un pointeur vers un objet constant comme le suivant:
template <typename T>
returnType function(const T* paramType);
int x = 23; // same as before
const int *pointer_x = &x; // pointer_x is pointer to x as const int
f(&x); // T is int, paramType is const int*
f(pointer_x); // T is int, paramType is const int*
c'est-à-dire, encore une fois const - ness n'est plus déduit comme une partie de T
Dans le cas de références de valeur r, tapez [~ # ~] t [~ # ~] et paramType la déduction suit essentiellement les mêmes règles que dans le cas des références de valeur l.
Cela couvre la majeure partie du premier cas. Regardons notre cas 2.
Les références universelles sont déclarées comme des références de valeur r mais prennent la valeur l, mais ce qui rend leur comportement différent, c'est que les arguments de fonction reçoivent des références de valeur l. Voici comment fonctionne la déduction de type pour ce cas:
(i) Si ( expression est une valeur l, les deux ( [~ # ~] t [~ # ~] et paramType sont déduits pour être l-valeur. (Cela semble étrange face à l'apparence du code car bien que paramType soit déclaré en utilisant la syntaxe de la valeur r référence, son type déduit est de référence l-value.) Il convient de noter que c'est le seul cas où [~ # ~] t [~ # ~] est déduit pour être une référence.
L'exemple ci-dessous clarifie mon explication:
template <typename T>
returnType function(T&& paramType); // param becomes universal reference if
// argument to function call is an l-value
int x = 23 // same as previous
const int const_x = x; // same as previous
const int& ref_x = x; // same as previous
f(x); // x is l-value therefore T is int&
// paramType is int&
f(const_x); // const_x is l-value therefore T is const int&
//paramType is also const int&
f(ref_x); // ref_x is l-value therefore T is const int&
// paramType is also const int&
f(23); // 27 is r-value so T is int
// paramType is now int&&
Je veux être honnête ici et dire que cela n'explique pas pourquoi les références universelles fonctionnent comme elles le font, mais je pense que ce message deviendra trop long si je continue à le justifier ici.
C'est là que passe-par-valeur dans le modèle, ce qui implique que param sera une copie de tout ce qui est passé à l'argument de la fonction appelante, c'est-à-dire un objet complètement nouveau, et cela motive les règles qui régissent la déduction de type de - [~ # ~] t [~ # ~] de l'expression . Deux points à noter ici sont:
(i) ignorer la réfrence - ness dans expression , s'il y en a un.
(ii) après avoir ignoré le ref - ness, ignorer const - ness ou volatile - également, c'est-à-dire s'il est présent
template <typename T>
returnType function(T paramType);
int x = 23;
const int const_x = x;
const int& ref_x = x;
f(x); // T and paramType are both int
f(const_x); // T and paramType are both int here too
f(ref_x); // T and paramType are both int again
Notez que même si const_x et ref_x sont des objets const qui ne peuvent pas être modifiés, cela ne signifie pas que leurs copies ne peuvent pas être modifiées. Cela semble simple, mais cela devient plus difficile lorsque nous passons un pointeur constant à un objet constant. Jetons un coup d'œil à un autre exemple:
template <typename T>
returnType function(T param);
const double *const dPtr = 23; // dPtr is const pointer to const double
function(dPtr); // passing argument of type const double *const
Lorsque const le pointeur est passé par valeur, le const - ness est perdu , et le pointeur est copié par valeur, ce qui est synchronisé avec les règles de déduction de type pour passer par valeur, mais la const - ness de quels points de pointeur à est préservé, et donc le paramètre paramType sera const * double.
Cela pourrait vous faire tourner la tête comme cela m'a été le cas lorsque j'ai commencé à en apprendre davantage. Le meilleur moyen serait de le relire et d'essayer de le coder.