Les références rvalue à const ont-elles une utilité?

Réponses:

78

Ils sont parfois utiles. Le projet C ++ 0x lui-même les utilise à quelques endroits, par exemple:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Les deux surcharges ci-dessus garantissent que l'autre ref(T&)cref(const T&) fonctions et ne se lient pas aux valeurs r (ce qui serait autrement possible).

Mise à jour

Je viens de vérifier la norme officielle N3290 , qui n'est malheureusement pas disponible publiquement, et elle contient en 20.8 les objets Function [function.objects] / p2:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Ensuite, j'ai vérifié le brouillon post-C ++ 11 le plus récent, qui est disponible publiquement, N3485 , et dans 20.8 Function objects [function.objects] / p2, il dit toujours:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
Howard Hinnant
la source
En regardant cppreference, il semble que ce n'est plus le cas. Des idées pourquoi? const T&&Est-ce que d' autres endroits sont utilisés?
Pubby
58
Pourquoi avez-vous inclus trois fois le même code dans votre réponse? J'ai essayé de trouver une différence pendant trop longtemps.
typ1232
9
@ typ1232: Il semble que j'ai mis à jour la réponse près de 2 ans après y avoir répondu, en raison de préoccupations dans les commentaires selon lesquelles les fonctions référencées n'apparaissaient plus. J'ai fait un copier / coller de N3290 et du dernier projet de N3485 pour montrer que les fonctions apparaissaient toujours. Utiliser le copier / coller, dans mon esprit à l'époque, était le meilleur moyen de s'assurer que plus d'yeux que les miens pouvaient confirmer que je n'ignorais pas un changement mineur dans ces signatures.
Howard Hinnant
1
@kevinarpe: Les "autres surcharges" (non montrées ici, mais dans le standard) prennent des références lvalue, et ne sont pas supprimées. Les surcharges présentées ici correspondent mieux à rvalues ​​que les surcharges qui prennent des références lvalue. Ainsi, les arguments rvalue se lient aux surcharges indiquées ici, puis provoquent une erreur de compilation car ces surcharges sont supprimées.
Howard Hinnant
1
L'utilisation de const T&&empêche quelqu'un d'utiliser bêtement des arguments de modèle explicites du formulaire ref<const A&>(...). Ce n'est pas un argument très fort, mais le coût du const T&&dépassement T&&est assez minime.
Howard Hinnant
6

La sémantique pour obtenir une référence const rvalue (et non pour =delete) consiste à dire:

  • nous ne prenons pas en charge l'opération pour lvalues!
  • même si, nous copions toujours , parce que nous ne pouvons pas déplacer la ressource passée, ou parce qu'il n'y a aucune signification réelle pour la "déplacer".

Le cas d'utilisation suivant aurait pu être à mon humble avis un bon cas d' utilisation de la référence rvalue à const , bien que le langage ait décidé de ne pas adopter cette approche (voir l'article original de SO ).


Le cas: constructeur de pointeurs intelligents à partir d'un pointeur brut

Il serait généralement conseillé d'utiliser make_uniqueet make_shared, mais les deux unique_ptret shared_ptrpeuvent être construits à partir d'un pointeur brut. Les deux constructeurs obtiennent le pointeur par valeur et le copient. Les deux permettent (c'est-à-dire au sens de: ne pas empêcher ) une utilisation continue du pointeur d'origine qui leur est passé dans le constructeur.

Le code suivant se compile et les résultats avec double free :

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

Les deux unique_ptret shared_ptrpourraient empêcher ce qui précède si leurs constructeurs concernés s'attendaient à obtenir le pointeur brut comme une valeur constante , par exemple pour unique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

Dans ce cas, le double code gratuit ci-dessus ne se compilerait pas, mais ce qui suit:

std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) };     // ok, rvalue

Notez que cela ptrpourrait encore être utilisé après avoir été déplacé, donc le bogue potentiel n'est pas totalement parti. Mais si l'utilisateur est obligé d'appeler std::moveun tel bogue tomberait dans la règle courante de: ne pas utiliser une ressource qui a été déplacée.


On peut se demander: OK, mais pourquoi T* const&& p ?

La raison est simple, pour permettre la création d'un unique_ptr pointeur depuis const . Rappelez-vous que la référence const rvalue est plus générique qu'une simple référence rvalue car elle accepte à la fois constet non-const. Nous pouvons donc permettre ce qui suit:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

cela n'irait pas si nous attendions juste une référence rvalue (erreur de compilation: impossible de lier const rvalue à rvalue ).


Quoi qu'il en soit, il est trop tard pour proposer une telle chose. Mais cette idée présente une utilisation raisonnable d'une référence rvalue à const .

Amir Kirsh
la source
J'étais parmi des personnes ayant des idées similaires, mais je n'avais pas le soutien nécessaire pour aller de l'avant.
Red.Wave
"auto p = std :: unique_ptr {std :: move (ptr)};" ne compile pas avec l'erreur «échec de la déduction de l'argument du modèle de classe». Je pense que ça devrait être "unique_ptr <int>".
Zehui Lin
4

Ils sont autorisés et même les fonctions classées en fonction const, mais comme vous ne pouvez pas vous déplacer d'un objet const référencé par const Foo&&, elles ne sont pas utiles.

Gene Bushuyev
la source
Qu'entendez-vous exactement par la remarque «classée»? Quelque chose à voir avec la résolution de surcharge, je suppose?
fredoverflow
Pourquoi ne pouviez-vous pas vous déplacer d'un const rvalue-ref, si le type donné a un move ctor qui prend un const rvalue-ref?
Fred Nurk
6
@FredOverflow, le classement des surcharges est le suivant:const T&, T&, const T&&, T&&
Gene Bushuyev
2
@Fred: Comment bougez-vous sans modifier la source?
fredoverflow
3
@Fred: les membres de données mutables, ou peut-être le déplacement pour ce type hypothétique, ne nécessitent pas de modification des membres de données.
Fred Nurk
2

Outre std :: ref , la bibliothèque standard utilise également la référence const rvalue dans std :: as_const dans le même but.

template <class T>
void as_const(const T&&) = delete;

Il est également utilisé comme valeur de retour dans std :: facultatif lors de l'obtention de la valeur encapsulée:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

Ainsi que dans std :: get :

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

Ceci est vraisemblablement pour conserver la catégorie de valeur ainsi que la constance du wrapper lors de l'accès à la valeur encapsulée.

Cela fait une différence si les fonctions qualifiées par const rvalue peuvent être appelées sur l'objet encapsulé. Cela dit, je ne connais aucune utilisation des fonctions qualifiées const rvalue ref.

Eerorika
la source
1

Je ne peux pas penser à une situation où cela serait utile directement, mais cela pourrait être utilisé indirectement:

template<class T>
void f(T const &x) {
  cout << "lvalue";
}
template<class T>
void f(T &&x) {
  cout << "rvalue";
}

template<class T>
void g(T &x) {
  f(T());
}

template<class T>
void h(T const &x) {
  g(x);
}

Le T en g est T const, donc f x est un T const &&.

Il est probable que cela entraîne une erreur de comile dans f (quand il essaie de déplacer ou d'utiliser l'objet), mais f pourrait prendre un rvalue-ref afin qu'il ne puisse pas être appelé sur lvalues, sans modifier la rvalue (comme dans le trop simple exemple ci-dessus).

Fred Nurk
la source