J’ai besoin de stocker des références universelles dans une classe (je suis sûr que les valeurs référencées survivront à la classe). Y a-t-il une manière canonique de le faire?
Voici un exemple minimal de ce que je suis venu avec. Cela semble fonctionner, mais je ne suis pas sûr d’avoir bien compris.
template struct binder { template binder(G&& g, Y&& y) : f(std::forward(g)), x(std::forward(y)) {} void operator()() { f(std::forward(x)); } F&& f; X&& x; }; template binder bind(F&& f, X&& x) { return binder(std::forward(f), std::forward(x)); } void test() { int i = 1; const int j = 2; auto f = [](int){}; bind(f, i)(); // X&& is int& bind(f, j)(); // X&& is const int& bind(f, 3)(); // X&& is int&& }
Mon raisonnement est-il correct ou cela mènera-t-il à des bugs subtils? En outre, existe-t-il une meilleure façon (c’est-à-dire plus concise) d’écrire le constructeur? binder(F&& f, X&& x)
ne fonctionnera pas, car ce sont des références de valeur r, ce qui interdit le binder(f, i)
.
Vous ne pouvez pas “stocker une référence universelle” car cela n’existe pas, il n’y a que des références rvalue et des références lvalue. “Référence universelle” est le terme qui convient à Scott Meyers pour décrire une fonctionnalité de syntaxe, mais cela ne fait pas partie du système de types .
Pour examiner des détails spécifiques du code:
template binder bind(F&& f, X&& x)
Ici, vous instanciez un binder
avec des types de référence en tant qu’arguments de modèle. Par conséquent, dans la définition de classe, il n’est pas nécessaire de déclarer les membres en tant que références rvalue, car ils sont déjà des types référence (lvalue ou rvalue déduits par bind
). Cela signifie que vous avez toujours plus de &&
jetons que nécessaire, qui sont redondants et disparaissent en raison de la réduction des références.
Si vous êtes sûr que binder
sera toujours instancié par votre fonction bind
(et donc toujours avec des types de référence), vous pouvez le définir comme ceci:
template struct binder { binder(F g, X y) : f(std::forward(g)), x(std::forward(y)) {} void operator()() { f(std::forward (x)); } F f; X x; };
Dans cette version, les types F
et X
sont des types de référence, il est donc redondant d’utiliser F&&
et X&&
car ils sont déjà des références à lvalue (donc le &&
ne fait rien) ou sont des références à la valeur rvalue (donc le &&
ne fait rien dans ce cas. aussi!)
Alternativement, vous pouvez garder le binder
tel que vous l’avez et changer de bind
en:
template binder bind(F&& f, X&& x) { return binder(std::forward(f), std::forward(x)); }
Maintenant, vous instanciez le binder
avec un type de référence lvalue ou un type d’object (c’est-à-dire non référencé), puis dans le binder
vous déclarez les membres avec le &&
supplémentaire afin qu’ils soient des types de référence lvalue ou de type rvalue.
De plus, si vous y réfléchissez, vous n’avez pas besoin de stocker des membres de référence rvalue. C’est parfaitement bien de stocker les objects par références lvalue, tout ce qui compte est de les transmettre correctement en tant que lvalues ou rvalues dans la fonction operator()
. Donc, les membres de la classe pourraient être juste F&
et X&
(ou dans le cas où vous instanciez toujours le type avec des arguments de référence de toute façon, F
et X
)
Donc, je voudrais simplifier le code à ceci:
template struct binder { binder(F& g, X& y) : f(g), x(y) { } void operator()() { f(std::forward(x)); } F& f; X& x; }; template binder bind(F&& f, X&& x) { return binder(f, x); }
Cette version conserve le type souhaité dans les parameters de modèle F
et X
et utilise le bon type dans l’expression std::forward
, qui est le seul endroit où cela est nécessaire.
Enfin, je trouve plus précis et plus utile de penser en termes de type déduit, pas seulement de type de référence (condensé):
bind(f, i)(); // X is int&, X&& is int& bind(f, j)(); // X is const int&, X&& is const int& bind(f, 3)(); // X is int, X&& is int&&
binder(F&& f, X&& x)
fonctionne bien .
F
et X
sont des types de référence (ce sont les types indiqués dans le binder
instanciation de modèle binder
). Ainsi, f
et x
deviennent des révérences universelles suivant les règles habituelles de réduction de référence.
En dehors de cela, le code est correct (tant que vous pouvez réellement vous assurer que les variables référencées survivent au classeur).