Comment stocker des références universelles

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(x) , 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).