1
votes

std :: facultatif: différence effective entre la valeur simple et la valeur qualifiée ref ()

Ceci est une question académique. Le type std::optional a une méthode T && value () && . J'ai les définitions suivantes:

std::optional<A> optA;
optA = A(1337);
f(std::move(optA).value()); // OPTION 1
f(std::move(optA.value())); // OPTION 2
std::cout << optA.has_value() << std::endl;

Et le programme suivant:

class A { ... };
void f(A &&a);

Y a-t-il une différence significative entre OPTION 1 et OPTION 2? Pour l'OPTION 1, aurai-je 1 , 0 ou non spécifié comme sortie? D'après mes tests, has_value () est resté vrai dans les deux cas.

Y a-t-il une situation possible où value () && fait une différence par rapport à std :: move code> et value()?


9 commentaires

Re: has_value () est resté true : cela dépend de f () . Modifiez-vous réellement a dans f () ?


@lorro Cela ne dépend en fait pas de f - notez que f obtient un A , il n'obtient pas optA . has_value () ne changerait que si f accédait d'une manière ou d'une autre à optA directement (en tant que global?)


@Barry: après avoir quitté un optionnel lui-même, il est dans un état valide mais indéterminé (à ma connaissance). Votre implémentation peut rester telle quelle. Ou il pourrait le définir sur nullopt. Ou toute valeur globale - hautement irréaliste mais possible, en particulier. avec des types d'agrégats. Au contraire, lorsque vous accédez à value (), il est garanti que l’option est conservée telle quelle, car c’est une référence non déplaçable.


@lorro Rien ici ne sort de l'option.


@Barry: std :: move (optA) .value () choisira valeur () && sur valeur ().


@lorro qui déplace une valeur stockée dans l'option. Cela ne déplace pas l'option. C'est semblable à std :: vector foo (100); auto x = std :: move (foo [0]); vs auto y = std :: move (foo); - on déplace quelque chose stocké dans le vecteur, l'autre se déplace le vecteur. Toutes les opérations déplacent quelque chose stocké dans l’option, aucune ne déplace l’option.


@ Yakk-AdamNevraumont: Pouvez-vous s'il vous plaît m'indiquer où la norme dit que std :: optional :: value () && (notez le && ) garantit que < code> * this n'est pas vide après l'appel (c'est-à-dire qu'il ne retourne pas le drapeau pour décrire s'il est vide - notez qu'il n'est pas nécessaire qu'il soit le même que pour détruire ou non l'objet dans le espace de rangement)? Ma connaissance actuelle est que value () && garantit que le optionnel est dans un état valide mais indéterminé, tandis que value () garantit qu'il reste non vide et l'objet contenu est dans un état valide mais indéterminé.


@lorro 23.6.3.5 [facultatif.observer] / 16? Comme dans, l'endroit où value () && est défini? "Équivalent à return bool (* this)? Std :: move (* val): throw bad_optional_access () ". L'opérateur * ne désactive pas le facultatif , et le déplacement des données dans l'option facultative peut ne pas changer l'état d'engagement de l'option. Je pourrais trouver le bit où «la bibliothèque standard n'est pas autorisée à vous déranger», mais il existe une règle générale selon laquelle lorsque vous interagissez avec l'objet A, vous ne pouvez pas jouer avec l'objet B à moins que la norme ne le dise.


@ Yakk-AdamNevraumont: Thx, TIL quelque chose. Donc dans ce cas, ce qui précède ne peut pas être fait par le compilateur et donc l'option optionnelle gardera sa valeur (si elle en avait une). Merci!


4 Réponses :


4
votes

Il n'y a aucune différence entre std :: move (optA) .value () et std :: move (optA.value ()) . value () renvoie simplement une valeur de glissement faisant référence à la valeur contenue, et soit vous pouvez avoir value () renvoyer une lvalue qui est ensuite convertie en une xvalue par std :: move , ou vous pouvez appeler std :: move d'abord et demander à value () de vous donner immédiatement une valeur x (en réalité, la conversion de lvalue to xvalue se produira quelque part dans la méthode value () elle-même). Les surcharges qualifiées ref ne sont évidemment pas très utiles dans ce cas simple, mais peuvent être utiles lorsque l'option optionnelle a été passée par la référence de transfert O && o , et que vous voulez std :: forward (o) .value () pour "faire ce qu'il faut".


0 commentaires

3
votes
f(std::move(optA.value())); // OPTION 2

2 commentaires

Votre première phrase n'a pas de sens. std :: move ne bouge pas. std :: move (x) ne déplace pas x . Dire que l'OP "a déménagé" optA n'a pas de sens (ou, plus précisément, est faux). Vous clarifiez plus tard, mais vous commencez par quelque chose qui n'est pas vrai. Confondre le problème avec de fausses déclarations n'est pas un excellent plan de réponse.


@ Yakk-AdamNevraumont: et vous me blâmez? ils l'ont appelé move après tout :) J'ai essayé de paraphraser la réponse, et j'ai échoué



1
votes

Y a-t-il une situation possible où value () && fait une différence par rapport à std :: move et value ()?

Tenez compte de ce qui suit:

struct C {string s;};
C func2() {...}

void h(string s);

h(func2().s);

f Le paramètre opt sera initialisé par move. Eh bien techniquement, il sera initialisé directement par la prvalue, mais pré-C ++ 17 signifie qu'il est initialisé par déplacement. Cette initialisation peut être éludée, mais si ce n'est pas le cas, elle se fait via move. Toujours.

Mais qu'en est-il du paramètre de g ? Que devrait-il arriver? Eh bien, considérez ce que cela ferait:

optional<A> func() {...}

void f(optional<A> opt) {...}
void g(A a) {...}

f(func());
g(func().value());

Le paramètre de h est initialisé par move . Pourquoi? Parce que si vous accédez à un sous-objet membre d'une prvalue, l'expression résultante est une xvalue, et est donc éligible pour le mouvement sans utiliser explicitement std::move.

Le && constructeur de facultatif garantit que la valeur fonctionne de la même manière. Si vous l'appelez sur une prvalue temporaire, alors il retournera une valeur x, qui peut être déplacée sans un appel explicite std :: move . Donc dans le cas d'origine, le paramètre de g est initialisé par move, exactement comme il l'aurait fait s'il accédait à un sous-objet membre d'une valeur pr.


0 commentaires

0
votes

Je suppose que les deux versions sont identiques, cependant, la réponse de Nicol Bolas semble intéressante.

En supposant qu'elles aboutissent toutes les deux à une rvalue et que le code n'a pas d'importance, nous n'avons aucune raison d'ignorer la lisibilité .

std::move(optA.value())

Cela suggère de déplacer la variable et de compter sur le rédacteur de la classe pour fournir les bonnes surcharges. S'il manque, vous risquez de le rater.

Certains linters reconnaissent également cela comme une variable à ne plus toucher, ce qui vous empêche de l'utiliser après le déplacement.

  std::move(optA).value()

Ceci convertit cependant la valeur de retour en une rvalue. Je considère surtout ce code comme un bogue. Soit la fonction retourne par valeur (ou référence const) et nous avons écrit beaucoup de code. Sinon, la fonction renvoie une lvalue et vous avez potentiellement ruiné l'état interne de la variable.

Avec cela, je recommande de déplacer systématiquement la variable et de déclencher le déplacement d'un appel de fonction comme une odeur de code. (Même pour std :: facultatif, là où cela ne devrait pas avoir d'importance)


0 commentaires