initializer_list et la sémantique de déplacement
Suis-je autorisé à déplacer les éléments 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?
}
}
Depuis std::intializer_list<T>
nécessite un compilateur attention et n'a pas de valeur sémantique comme les conteneurs normaux de la norme C++ de la bibliothèque, je préfère être sûr que désolé et demander.
- Le langage de base définit que l'objet visé par une
initializer_list<T>
sont non-const. Comme,initializer_list<int>
se réfère àint
objets. Mais je pense que c'est un défaut, il est prévu que les compilateurs peuvent allouer de manière statique une liste en mémoire en lecture seule.
Vous devez vous connecter pour publier un commentaire.
Non, ça ne fonctionne pas comme prévu, vous aurez toujours une copie. Je suis assez surpris par cela, comme je l'avais pensé que
initializer_list
permettaient de tenir un tableau de temporaires jusqu'à ce qu'ils étaientmove
'd.begin
etend
pourinitializer_list
retourconst T *
, de sorte que le résultat demove
dans votre code estT const &&
— immuable référence rvalue. Une telle expression ne peut pas véritablement être déplacé d'. Il va se lier à un paramètre de fonction de typeT const &
parce que rvalues ne se lier à const lvalue références, et vous verrez toujours la sémantique de copie.Probablement la raison pour cela est que le compilateur peut choisir d'effectuer le
initializer_list
une manière statique initialisé constante, mais il semble qu'il serait plus propre de faire son typeinitializer_list
ouconst initializer_list
au compilateur de la discrétion, de sorte que l'utilisateur ne sait pas s'attendre à uneconst
ou mutable résultat debegin
etend
. Mais c'est juste mon sentiment, il y a probablement une bonne raison je me suis trompé.Mise à jour: j'ai écrit une proposition de l'ISO pour
initializer_list
soutien de déplacer uniquement les types. C'est seulement une première ébauche, et il n'est pas mis en œuvre partout, mais vous pouvez le voir, pour une analyse plus approfondie du problème.std::move
est sûr, si ce n'est productif. (SaufT const&&
constructeurs de déplacement.)const std::initializer_list<T>
ou tout simplementstd::initializer_list<T>
de manière à ne pas causer des surprises assez souvent. Considérer que chaque argument dans leinitializer_list
peut êtreconst
ou non, et qui est connu dans le contexte de l'appelant, mais le compilateur doit générer qu'une version du code dans le contexte de l'appelé (c'est à dire à l'intérieur defoo
il ne sait rien sur les arguments que l'appelant est en passant)std::initializer_list &&
surcharge de faire quelque chose, même si un non-référence de surcharge est également nécessaire. Je suppose qu'il serait encore plus déroutant que de la situation actuelle, qui est déjà mauvais.Pas dans le sens que vous souhaitez. Vous ne pouvez pas déplacer un
const
objet. Etstd::initializer_list
ne fournitconst
l'accès à ses éléments. Donc, le type deit
estconst T *
.Votre tentative de faire appel
std::move(*it)
ne fait qu'une l-value. C'est à dire: une copie.std::initializer_list
références statique de la mémoire. C'est ce que la classe est destinée. Vous ne pouvez pas déplacer de mémoire statique, car le mouvement implique le changement. Vous ne pouvez copier à partir d'elle.initializer_list
références de la pile si nécessaire. (Si le contenu n'est pas constant, il est encore thread-safe.)initializer_list
objet lui-même peut être une value, mais c'est le contenu (le tableau de valeurs qu'il pointe vers) sontconst
, parce que ces contenus peuvent être des valeurs statiques. Vous ne peut tout simplement pas se déplacer à partir du contenu d'uninitializer_list
.const
value.move
peut être inutile, mais c'est légal et même possible de déclarer un paramètre qui accepte que. Si le déplacement d'un type particulier se trouve être un no-op, il peut même fonctionner correctement.std::move
. Cela garantit que vous pouvez dire à partir d'inspection lors d'une opération de déplacement qui se passe, car il affecte à la fois la source et la destination (vous ne voulez pas qu'il arrive implicitement pour les objets nommés). À cause de cela, si vous utilisezstd::move
dans un endroit où une opération de déplacement ne pas arriver (et pas de mouvement réel qui va vous arriver si vous avez unconst
value), puis le code est trompeuse. Je pense que c'est une erreur pourstd::move
d'être racheté surconst
objet.const &&
dans un garbage collector de classe avec la gestion des pointeurs, où tout est mutable et le déplacement déplacé le pointeur de la direction, mais n'affecte pas le contenu de la valeur. Il y a toujours délicat de bord de cas :v) .Cela ne fonctionne pas comme indiqué, parce que
list.begin()
a typeconst T *
, et il n'y a aucun moyen que vous pouvez déplacer à partir d'un objet constant. La langue concepteurs probablement fait que c'est dans le but de permettre à l'initialiseur listes contiennent par exemple des constantes de chaîne, à partir de laquelle il serait inapproprié de se déplacer.Toutefois, si vous êtes dans une situation où vous savez que l'initialiseur liste contient rvalue expressions (ou si vous voulez forcer l'utilisateur à écrire ces) puis il y a un truc qui va faire fonctionner (j'ai été inspiré par la réponse par Sumant pour cela, mais la solution est plus simple que ça). Vous avez besoin des éléments stockés dans les initialiser la liste pour ne pas être
T
valeurs, mais les valeurs qui encapsulentT&&
. Alors, même si ces valeurs elles-mêmes sontconst
qualifiés, ils peuvent toujours récupérer modifiable rvalue.Maintenant, au lieu de déclarer une
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 qui seule la sémantique de déplacement est défini (de sorte que ces objets eux-mêmes ne peuvent jamais être stockés dans une liste d'initialiseur); cependant, l'initialiseur de la liste ci-dessous compile sans problème.Une question nécessite une réponse: si les éléments de la liste d'initialiseur doit être vrai prvalues (dans l'exemple, ils sont xvalues), la langue s'assurer que la durée de vie de l'correspondant temporaires s'étend jusqu'au point où ils sont utilisés? Franchement, je ne pense pas que la section correspondante de l'article 8.5 de la norme traite de cette question. Cependant, la lecture de 1.9:10, il semblerait que la pleine expression dans tous les cas, englobe l'utilisation de la liste d'initialiseur, donc je pense qu'il n'y a pas de danger, en balançant les références rvalue.
"Hello world"
? Si vous passer d'elles, il vous suffit de copier un pointeur (ou lier une référence).{..}
sont liés à des références dans le paramètre de la fonction derref_capture
. Cela ne veut pas prolonger 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 certains de transformation trait de caractère de votre choix - dire,std::decay_t
- pour bloquer les indésirables de la déduction.J'ai pensé qu'il pourrait être intéressant d'offrir un point de départ raisonnable pour une solution de contournement.
Commentaires en ligne.
initializer_list
peut être déplacé à partir, de ne pas savoir si quelqu'un avait des solutions de rechange. Par ailleurs, le principal point de vente deinitializer_list
est qu'il est uniquement basé sur un modèle sur le type d'élément, et non le nombre d'éléments, et ne nécessite donc pas bénéficiaires à être basé sur un modèle - et cela a complètement perd que.initializer_list
mais ne sont pas soumis à toutes les contraintes qui font qu'il est utile. 🙂initializer_list
(via le compilateur de la magie) évite de modèle de fonctions sur le nombre d'éléments, de quelque chose qui est fondamentalement nécessaires par des solutions basées sur des tableaux et/ou de variadic fonctions, limitant ainsi l'éventail des cas où ces derniers sont utilisables. Selon ma compréhension, c'est précisément l'une des principales justifications pour avoirinitializer_list
, il m'a donc semblé utile de mentionner.Il ne semble pas autorisés dans la norme actuelle comme déjà répondu. Voici une autre solution pour obtenir quelque chose de similaire, par définition de la fonction comme variadic au lieu de prendre un initialiseur de liste.
Variadic templates peut gérer la r-valeur de références de manière appropriée, à la différence de initializer_list.
Dans cet exemple de code, j'ai utilisé un ensemble de petites fonctions d'aide à convertir les variadic arguments dans un vecteur, pour la rendre semblable à l'original du code. Mais bien sûr, vous pouvez écrire une fonction récursive avec variadic templates directement à la place.
initializer_list
peut être déplacé à partir, de ne pas savoir si quelqu'un avait des solutions de rechange. Par ailleurs, le principal point de vente deinitializer_list
est qu'il est uniquement basé sur un modèle sur le type d'élément, et non le nombre d'éléments, et ne nécessite donc pas bénéficiaires à être basé sur un modèle - et cela a complètement perd que.Envisager la
in<T>
idiome décrit sur cpptruths. L'idée est de déterminer lvalue/rvalue au moment de l'exécution, puis appel de déplacer ou de copier-construction.in<T>
détecte rvalue/lvalue même si l'interface standard fourni par initializer_list est const référence.