Suis-je autorisé à déplacer des éléments hors d'un std::initializer_list<T>
?
#include <initializer_list>
#include <utility>
template<typename T>
void foo(std::initializer_list<T> list)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
bar(std::move(*it)); // kosher?
}
}
Puisque std::intializer_list<T>
nécessite une attention particulière du compilateur et n'a pas de sémantique de valeur comme les conteneurs normaux de la bibliothèque standard C ++, je préfère prévenir que guérir et demander.
c++
templates
c++11
move-semantics
initializer-list
fredoverflow
la source
la source
initializer_list<T>
sont pas -const. Comme,initializer_list<int>
fait référence auxint
objets. Mais je pense que c'est un défaut - il est prévu que les compilateurs puissent allouer statiquement une liste en mémoire morte.Réponses:
Non, cela ne fonctionnera pas comme prévu; vous obtiendrez toujours des copies. Je suis assez surpris par cela, car je pensais que cela
initializer_list
existait pour garder un éventail de temporaires jusqu'à ce qu'ils le soientmove
.begin
etend
pour leinitializer_list
retourconst T *
, le résultat demove
dans votre code estT const &&
- une référence rvalue immuable. Une telle expression ne peut pas être déplacée de manière significative. Il se liera à un paramètre de fonction de typeT const &
car rvalues se lie aux références const lvalue et vous verrez toujours la sémantique de copie.La raison en est probablement que le compilateur peut choisir de créer
initializer_list
une constante initialisée statiquement, mais il semble qu'il serait plus propre de créer son typeinitializer_list
ouconst initializer_list
à la discrétion du compilateur, de sorte que l'utilisateur ne sait pas s'il doit s'attendre à unconst
ou mutable résultent debegin
etend
. Mais c'est juste mon instinct, il y a probablement une bonne raison pour laquelle je me trompe.Mise à jour: j'ai écrit une proposition ISO pour la
initializer_list
prise en charge des types de déplacement uniquement. Ce n'est qu'un premier projet, et il n'est encore implémenté nulle part, mais vous pouvez le voir pour une analyse plus approfondie du problème.la source
std::move
est sûre, sinon productive. (Sauf lesT const&&
constructeurs de mouvement.)const std::initializer_list<T>
ou simplementstd::initializer_list<T>
d'une manière qui ne cause pas souvent de surprises. Considérez que chaque argument dans leinitializer_list
peut être l'unconst
ou l' autre et cela est connu dans le contexte de l'appelant, mais le compilateur doit générer une seule version du code dans le contexte de l'appelé (c'est-à-dire qu'à l'intérieur,foo
il ne sait rien des arguments que l'appelant passe)std::initializer_list &&
surcharge fasse quelque chose, même si une surcharge sans référence est également requise. Je suppose que ce serait encore plus déroutant que la situation actuelle, qui est déjà mauvaise.Pas de la manière dont vous l'entendez. Vous ne pouvez pas déplacer un
const
objet. Etstd::initializer_list
ne donneconst
accès qu'à ses éléments. Donc, le type deit
estconst T *
.Votre tentative d'appel
std::move(*it)
n'entraînera qu'une valeur L. IE: une copie.std::initializer_list
fait référence à la mémoire statique . C'est à cela que sert la classe. Vous ne pouvez pas sortir de la mémoire statique, car le mouvement implique de la changer. Vous ne pouvez en copier que.la source
initializer_list
référence à la pile si cela est nécessaire. (Si le contenu n'est pas constant, il est toujours thread-safe.)initializer_list
objet lui-même peut être une valeur x, mais son contenu (le tableau réel de valeurs vers lequel il pointe) l'estconst
, car ces contenus peuvent être des valeurs statiques. Vous ne pouvez tout simplement pas vous déplacer du contenu d'un fichierinitializer_list
.const
x.move
peut ne pas avoir de sens, mais il est légal et même possible de déclarer un paramètre qui accepte exactement cela. Si le déplacement d'un type particulier s'avère être un non-op, cela peut même fonctionner correctement.std::move
. Cela garantit que vous pouvez dire à partir de l'inspection quand une opération de déplacement se produit, car elle affecte à la fois la source et la destination (vous ne voulez pas que cela se produise implicitement pour les objets nommés). Pour cette raison, si vous utilisezstd::move
dans un endroit où une opération de déplacement ne se produit pas (et aucun mouvement réel ne se produira si vous avez une valeurconst
x), alors le code est trompeur. Je pense que c'est une erreurstd::move
d'être appelable sur unconst
objet.const &&
dans une classe garbage collection avec des pointeurs gérés, où tout ce qui était pertinent était mutable et le déplacement déplaçait la gestion du pointeur mais n'affectait pas la valeur contenue. Il y a toujours des cas extrêmes délicats: v).Cela ne fonctionnera pas comme indiqué, car
list.begin()
a un typeconst T *
et il n'y a aucun moyen de vous déplacer à partir d'un objet constant. Les concepteurs de langage ont probablement fait cela pour permettre aux listes d'initialiseurs de contenir par exemple des constantes de chaîne, à partir desquelles il serait inapproprié de se déplacer.Cependant, si vous êtes dans une situation où vous savez que la liste d'initialisation contient des expressions rvalue (ou que vous voulez forcer l'utilisateur à les écrire), alors il y a une astuce qui le fera fonctionner (j'ai été inspiré par la réponse de Sumant pour ceci, mais la solution est bien plus simple que celle-là). Vous avez besoin que les éléments stockés dans la liste d'initialisation ne soient pas des
T
valeurs, mais des valeurs qui encapsulentT&&
. Ensuite, même si ces valeurs elles-mêmes sontconst
qualifiées, elles peuvent toujours récupérer une rvalue modifiable.Maintenant, au lieu de déclarer un
initializer_list<T>
argument, vous déclarez uninitializer_list<rref_capture<T> >
argument. Voici un exemple concret, impliquant un vecteur destd::unique_ptr<int>
pointeurs intelligents, pour lequel seule la sémantique de déplacement est définie (donc ces objets eux-mêmes ne peuvent jamais être stockés dans une liste d'initialisation); pourtant la liste des initialiseurs ci-dessous se compile sans problème.Une question nécessite une réponse: si les éléments de la liste d'initialisation doivent être de vraies valeurs prvalues (dans l'exemple ce sont des valeurs x), le langage garantit-il que la durée de vie des temporels correspondants s'étend jusqu'au point où ils sont utilisés? Franchement, je ne pense pas que l'article 8.5 pertinent de la norme traite du tout de cette question. Cependant, à la lecture de 1.9: 10, il semblerait que l' expression complète pertinente dans tous les cas englobe l'utilisation de la liste d'initialiseurs, donc je pense qu'il n'y a aucun danger de suspendre les références rvalue.
la source
"Hello world"
? Si vous vous en éloignez, vous copiez simplement un pointeur (ou liez une référence).{..}
sont liés à des références dans le paramètre de fonction derref_capture
. Cela ne prolonge pas leur durée de vie, ils sont toujours détruits à la fin de la pleine expression dans laquelle ils ont été créés.std::initializer_list<rref_capture<T>>
dans un trait de transformation de votre choix - disonsstd::decay_t
- pour bloquer les déductions indésirables.J'ai pensé qu'il pourrait être instructif d'offrir un point de départ raisonnable pour une solution de contournement.
Commentaires en ligne.
la source
initializer_list
peut être déplacé, pas si quelqu'un avait des solutions de contournement. En outre, le principal argument de vente deinitializer_list
est qu'il est uniquement basé sur le type d'élément, pas sur le nombre d'éléments, et ne nécessite donc pas que les destinataires soient également modèles - et cela perd complètement cela.initializer_list
mais ne sont pas soumis à toutes les contraintes qui le rendent utile. :)initializer_list
(via la magie du compilateur) évite d'avoir à modéliser des fonctions sur le nombre d'éléments, ce qui est intrinsèquement requis par les alternatives basées sur des tableaux et / ou des fonctions variadiques, limitant ainsi la gamme de cas où ces derniers sont utilisables. D'après ce que j'ai compris, c'est précisément l'une des principales raisons de l'avoirinitializer_list
, donc cela semblait intéressant de le mentionner.Cela ne semble pas autorisé dans la norme actuelle comme déjà répondu . Voici une autre solution de contournement pour obtenir quelque chose de similaire, en définissant la fonction comme variadique au lieu de prendre une liste d'initialiseurs.
Les modèles Variadic peuvent gérer les références de valeur r de manière appropriée, contrairement à initializer_list.
Dans cet exemple de code, j'ai utilisé un ensemble de petites fonctions d'assistance pour convertir les arguments variadiques en un vecteur, pour le rendre similaire au code d'origine. Mais bien sûr, vous pouvez écrire directement une fonction récursive avec des modèles variadiques.
la source
initializer_list
peut être déplacé, pas si quelqu'un avait des solutions de contournement. En outre, le principal argument de vente deinitializer_list
est qu'il est uniquement basé sur le type d'élément, pas sur le nombre d'éléments, et ne nécessite donc pas que les destinataires soient également modèles - et cela perd complètement cela.J'ai une implémentation beaucoup plus simple qui utilise une classe wrapper qui agit comme une balise pour marquer l'intention de déplacer les éléments. Il s'agit d'un coût de compilation.
La classe wrapper est conçue pour être utilisée de la manière
std::move
utilisée, il suffit de la remplacerstd::move
parmove_wrapper
, mais cela nécessite C ++ 17. Pour les spécifications plus anciennes, vous pouvez utiliser une méthode de générateur supplémentaire.Vous devrez écrire des méthodes / constructeurs de générateur qui acceptent les classes wrapper à l'intérieur
initializer_list
et déplacer les éléments en conséquence.Si vous avez besoin que certains éléments soient copiés au lieu d'être déplacés, créez une copie avant de la transmettre
initializer_list
.Le code doit être auto-documenté.
la source
Au lieu d'utiliser a
std::initializer_list<T>
, vous pouvez déclarer votre argument en tant que référence de tableau rvalue:Voir l'exemple utilisant
std::unique_ptr<int>
: https://gcc.godbolt.org/z/2uNxv6la source
Considérez l'
in<T>
idiome décrit sur cpptruths . L'idée est de déterminer lvalue / rvalue au moment de l'exécution, puis d'appeler move ou copy-construction.in<T>
détectera rvalue / lvalue même si l'interface standard fournie par initializer_list est const reference.la source