Quels manipulateurs iomanip sont «collants»?

140

J'ai récemment eu un problème pour créer un en stringstreamraison du fait que j'ai supposé à tort que std::setw()cela affecterait le flux de chaînes pour chaque insertion, jusqu'à ce que je le modifie explicitement. Cependant, il est toujours désactivé après l'insertion.

// With timestruct with value of 'Oct 7 9:04 AM'
std::stringstream ss;
ss.fill('0'); ss.setf(ios::right, ios::adjustfield);
ss << setw(2) << timestruct.tm_mday;
ss << timestruct.tm_hour;
ss << timestruct.tm_min;
std::string filingTime = ss.str(); // BAD: '0794'

Donc, j'ai un certain nombre de questions:

  • Pourquoi est- setw()ce ainsi?
  • Y a-t-il d'autres manipulateurs de cette façon?
  • Y a-t-il une différence de comportement entre std::ios_base::width()et std::setw()?
  • Enfin, existe-t-il une référence en ligne qui documente clairement ce comportement? La documentation de mon fournisseur (MS Visual Studio 2005) ne semble pas le montrer clairement.
John K
la source
Un tour de travail est ici: stackoverflow.com/a/37495361/984471
Manohar Reddy Poreddy

Réponses:

87

Notes importantes tirées des commentaires ci-dessous:

Par Martin:

@Chareles: Alors par cette exigence, tous les manipulateurs sont collants. Sauf setw qui semble être réinitialisé après utilisation.

Par Charles:

Exactement! et la seule raison pour laquelle setw semble se comporter différemment est qu'il existe des exigences sur les opérations de sortie formatées pour explicitement .width (0) le flux de sortie.

Voici la discussion qui mène à la conclusion ci-dessus:


En regardant le code, les manipulateurs suivants renvoient un objet plutôt qu'un flux:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

Il s'agit d'une technique courante pour appliquer une opération uniquement à l'objet suivant appliqué au flux. Malheureusement, cela ne les empêche pas d'être collants. Les tests indiquent que tous sauf setwsont collants.

setiosflags:  Sticky
resetiosflags:Sticky
setbase:      Sticky
setfill:      Sticky
setprecision: Sticky

Tous les autres manipulateurs renvoient un objet stream. Ainsi, toute information d'état qu'ils changent doit être enregistrée dans l'objet de flux et est donc permanente (jusqu'à ce qu'un autre manipulateur change l'état). Ainsi, les manipulateurs suivants doivent être des manipulateurs collants .

[no]boolalpha
[no]showbase
[no]showpoint
[no]showpos
[no]skipws
[no]unitbuf
[no]uppercase

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

Ces manipulateurs effectuent en fait une opération sur le flux lui-même plutôt que sur l'objet de flux (bien que techniquement, le flux fasse partie de l'état des objets de flux). Mais je ne pense pas qu'ils affectent une autre partie de l'état des objets de flux.

ws/ endl/ ends/ flush

La conclusion est que setw semble être le seul manipulateur de ma version à ne pas coller.

Pour Charles, une astuce simple pour n'affecter que l'élément suivant de la chaîne:
Voici un exemple comment un objet peut être utilisé pour changer temporairement l'état puis le remettre en utilisant un objet:

#include <iostream>
#include <iomanip>

// Private object constructed by the format object PutSquareBracket
struct SquareBracktAroundNextItem
{
    SquareBracktAroundNextItem(std::ostream& str)
        :m_str(str)
    {}
    std::ostream& m_str;
};

// New Format Object
struct PutSquareBracket
{};

// Format object passed to stream.
// All it does is return an object that can maintain state away from the
// stream object (so that it is not STICKY)
SquareBracktAroundNextItem operator<<(std::ostream& str,PutSquareBracket const& data)
{
    return SquareBracktAroundNextItem(str);
}

// The Non Sticky formatting.
// Here we temporariy set formating to fixed with a precision of 10.
// After the next value is printed we return the stream to the original state
// Then return the stream for normal processing.
template<typename T>
std::ostream& operator<<(SquareBracktAroundNextItem const& bracket,T const& data)
{
    std::ios_base::fmtflags flags               = bracket.m_str.flags();
    std::streamsize         currentPrecision    = bracket.m_str.precision();

    bracket.m_str << '[' << std::fixed << std::setprecision(10) << data << std::setprecision(currentPrecision) << ']';

    bracket.m_str.flags(flags);

    return bracket.m_str;
}


int main()
{

    std::cout << 5.34 << "\n"                        // Before 
              << PutSquareBracket() << 5.34 << "\n"  // Temp change settings.
              << 5.34 << "\n";                       // After
}


> ./a.out 
5.34
[5.3400000000]
5.34
Martin York
la source
Belle feuille de triche. Ajoutez une référence à l'origine de l'information, et ce serait une réponse parfaite.
Mark Ransom
1
Cependant, je peux vérifier que setfill () est en fait «collant» bien qu'il renvoie un objet. Je pense donc que cette réponse n'est pas correcte.
John K
2
Les objets qui renvoient un flux doivent être persistants, tandis que ceux qui renvoient un objet peuvent être persistants mais ce n'est pas obligatoire. Je mettrai à jour la réponse avec John's Info.
Martin York
1
Je ne suis pas sûr de comprendre votre raisonnement. Tous les manipulateurs qui prennent des paramètres sont implémentés en tant que fonctions libres retournant un objet non spécifié qui agit sur un flux lorsque cet objet est inséré dans le flux car c'est la seule (?) Façon de préserver la syntaxe d'insertion avec des paramètres. Dans tous les cas, le paramètre approprié operator<<pour le manipulateur garantit que l'état du flux est modifié d'une certaine manière. Aucune des deux formes ne met en place une sorte de sentinelle d'État. Seul le comportement de la prochaine opération d'insertion formatée détermine quelle partie de l'état est réinitialisée, le cas échéant.
CB Bailey
3
Exactement! et la seule raison qui setwsemble se comporter différemment est qu'il existe des exigences sur les opérations de sortie formatées pour explicitement .width(0)le flux de sortie.
CB Bailey
31

La raison qui widthne semble pas être «collante» est que certaines opérations sont assurées d'appeler .width(0)un flux de sortie. Ce sont:

21.3.7.9 [lib.string.io]:

template<class charT, class traits, class Allocator>
  basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>& os,
               const basic_string<charT,traits,Allocator>& str);

22.2.2.2.2 [lib.facet.num.put.virtuals]: Toutes les do_putsurcharges pour le num_putmodèle. Ceux-ci sont utilisés par les surcharges de operator<<prise d'un basic_ostreamet d'un type numérique intégré.

22.2.6.2.2 [lib.locale.money.put.virtuals]: Toutes les do_putsurcharges pour le money_putmodèle.

27.6.2.5.4 [lib.ostream.inserters.character]: surcharges de operator<<prise de a basic_ostreamet un du type char de l'instanciation basic_ostream ou char, signé charou unsigned charou pointeurs vers des tableaux de ces types char.

Pour être honnête, je ne suis pas sûr de la justification de cela, mais aucun autre état d'un ostreamne doit être réinitialisé par des fonctions de sortie formatées. Bien sûr, des éléments tels que badbitet failbitpeuvent être définis en cas d'échec de l'opération de sortie, mais cela devrait être prévisible.

La seule raison à laquelle je peux penser pour réinitialiser la largeur est qu'il pourrait être surprenant que, lorsque vous essayez de générer des champs délimités, vos délimiteurs aient été remplis.

Par exemple

std::cout << std::setw(6) << 4.5 << '|' << 3.6 << '\n';

"   4.5     |   3.6      \n"

Pour `` corriger '' cela, il faudrait:

std::cout << std::setw(6) << 4.5 << std::setw(0) << '|' << std::setw(6) << 3.6 << std::setw(0) << '\n';

alors qu'avec une largeur de réinitialisation, la sortie souhaitée peut être générée avec le plus court:

std::cout << std::setw(6) << 4.5 << '|' << std::setw(6) << 3.6 << '\n';
CB Bailey
la source
6

setw()n'affecte que la prochaine insertion. C'est juste la façon dont setw()se comporte. Le comportement de setw()est le même que ios_base::width(). J'ai obtenu mes setw()informations de cplusplus.com .

Vous pouvez trouver une liste complète des manipulateurs ici . À partir de ce lien, tous les indicateurs de flux devraient indiquer ensemble jusqu'à ce qu'ils soient modifiés par un autre manipulateur. Une note sur le left, rightet internalManipulateurs: Ils sont comme les autres drapeaux et ne persistent jusqu'à la prochaine modification. Cependant, ils n'ont d'effet que lorsque la largeur du flux est définie et la largeur doit être définie à chaque ligne. Donc, par exemple

cout.width(6);
cout << right << "a" << endl;
cout.width(6);
cout << "b" << endl;
cout.width(6);
cout << "c" << endl;

te donnerait

>     a
>     b
>     c

mais

cout.width(6);
cout << right << "a" << endl;
cout << "b" << endl;
cout << "c" << endl;

te donnerait

>     a
>b
>c

Les manipulateurs d'entrée et de sortie ne sont pas collants et n'apparaissent qu'une seule fois là où ils sont utilisés. Les manipulateurs paramétrés sont chacun différents, voici une brève description de chacun:

setiosflagsvous permet de définir manuellement des indicateurs, dont une liste peut être affichée ici , donc elle est collante.

resetiosflagsse comporte de la même manière que setiosflagssauf qu'il annule les indicateurs spécifiés.

setbase définit la base des nombres entiers insérés dans le flux (donc 17 en base 16 serait "11", et en base 2 serait "10001").

setfilldéfinit le caractère de remplissage à insérer dans le flux lorsqu'il setwest utilisé.

setprecision définit la précision décimale à utiliser lors de l'insertion de valeurs à virgule flottante.

setw fait seulement la prochaine insertion de la largeur spécifiée en remplissant avec le caractère spécifié dans setfill

David Brown
la source
Eh bien, la plupart d'entre eux ne font que définir des indicateurs, donc ceux-ci sont "collants". setw () semble être le seul qui n'affecte qu'une seule insertion. Vous pouvez trouver plus de détails pour chacun sur cplusplus.com/reference/iostream/manipulators
David Brown
Eh bien non std::hexplus n'est pas collant et, évidemment, std::flushou std::setiosflagsn'est pas collant non plus. Donc je ne pense pas que ce soit aussi simple.
sbi
Juste en testant hex et setiosflags (), ils semblent tous les deux collants (ils définissent tous les deux simplement des indicateurs qui persistent pour ce flux jusqu'à ce que vous les changiez).
David Brown
Ouais, la page Web qui prétendait std::hexne pas être collante était fausse - je viens de le découvrir aussi. Les indicateurs de flux, cependant, peuvent changer même si vous n'insérez pas de std::setiosflagsnouveau, donc on pourrait voir cela comme non collant. En outre, std::wsn'est pas collant non plus. Ce n’est donc pas si simple.
sbi
Vous avez fait beaucoup d'efforts pour améliorer votre réponse. +1
sbi