initializer_list et déplacer la sémantique

Suis-je autorisé à déplacer des éléments d’un std::initializer_list ?

 #include  #include  template void foo(std::initializer_list list) { for (auto it = list.begin(); it != list.end(); ++it) { bar(std::move(*it)); // kosher? } } 

Étant donné que std::intializer_list requirejs 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 être prudent que désolé et demander.

Non, cela ne fonctionnera pas comme prévu vous aurez toujours des copies. Je suis assez surpris par cela, car je pensais initializer_list existait pour garder un tableau de tâches temporaires jusqu’à ce qu’elles soient move .

begin and end pour initializer_list return const T * , donc le résultat du move dans votre code est T const && – une référence de valeur immuable. Une telle expression ne peut être déplacée de manière significative. Il se liera à un paramètre de fonction de type T const & parce que les valeurs de valeur sont liées aux références const lvalue, et vous verrez toujours la sémantique de la copie.

La raison en est probablement que le compilateur peut choisir d’ initializer_list la liste initializer_list une constante statiquement initialisée, mais il semblerait qu’il soit plus propre de rendre son type initializer_list ou const initializer_list à la discrétion du compilateur. attendez-vous à un résultat const ou modifiable de begin et de end . 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 le support initializer_list des types à déplacement uniquement. Ce n’est qu’un premier brouillon, et il n’est pas encore implémenté, mais vous pouvez le voir pour une parsing plus approfondie du problème.

 bar(std::move(*it)); // kosher? 

Pas de la façon dont vous avez l’intention. Vous ne pouvez pas déplacer un object const . Et std::initializer_list fournit uniquement un access const à ses éléments. Le type est donc const T * .

Votre tentative d’appeler std::move(*it) ne donnera qu’une valeur l. IE: une copie.

std::initializer_list référence à la mémoire statique . C’est pour ça que la classe est. Vous ne pouvez pas vous déplacer de la mémoire statique, car le mouvement implique de le changer. Vous ne pouvez en copier que

Cela ne fonctionnera pas comme indiqué, car list.begin() a le type const T * , et vous ne pouvez pas vous déplacer d’un object constant. Les concepteurs de langage l’ont probablement fait pour permettre aux listes d’initialisation de contenir, par exemple, des constantes de chaîne, dont il serait inapproprié de 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), il y a un truc qui le fera fonctionner (la réponse de Sumant ceci, mais la solution est beaucoup plus simple que celle-là). Les éléments stockés dans la liste d’initialisation doivent être non T valeurs T , mais des valeurs qui encapsulent T&& . Alors même si ces valeurs elles-mêmes sont qualifiées de const , elles peuvent toujours récupérer une valeur modifiable.

 template class rref_capture { T* ptr; public: rref_capture(T&& x) : ptr(&x) {} operator T&& () const { return std::move(*ptr); } // restitute rvalue ref }; 

Maintenant, au lieu de déclarer un argument initializer_list , vous déclarez un argument initializer_list > . Voici un exemple concret, impliquant un vecteur de std::unique_ptr pointeurs intelligents, pour lesquels seule la sémantique de déplacement est définie (donc ces objects eux-mêmes ne peuvent jamais être stockés dans une liste d’initialisation); pourtant la liste d’initialisation ci-dessous comstack sans problème.

 #include  #include  class uptr_vec { typedef std::unique_ptr uptr; // move only type std::vector data; public: uptr_vec(uptr_vec&& v) : data(std::move(v.data)) {} uptr_vec(std::initializer_list > l) : data(l.begin(),l.end()) {} uptr_vec& operator=(const uptr_vec&) = delete; int operator[] (size_t index) const { return *data[index]; } }; int main() { std::unique_ptr a(new int(3)), b(new int(1)),c(new int(4)); uptr_vec v { std::move(a), std::move(b), std::move(c) }; std::cout << v[0] << "," << v[1] << "," << v[2] << std::endl; } 

Une question nécessite une réponse: si les éléments de la liste d'initialisation doivent être de véritables valeurs (dans l'exemple, ils sont des xvalues), le langage garantit-il que la durée de vie des temporaires correspondantes s'étend jusqu'au point d'utilisation? Franchement, je ne pense pas que la section 8.5 pertinente de la norme traite de cette question du tout. Cependant, en lisant 1.9: 10, il semblerait que la pleine expression dans tous les cas englobe l’utilisation de la liste d’initialisation, donc je pense qu’il n’ya aucun risque de perdre des références à la valeur.