Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Effective use of C++ iomanip library

I created a Vector class in C++ and it works great for my problems. I am now cleaning it up, and I ran into the following piece of code:

std::ostream& operator<<(std::ostream &output, const Vector &v){
  output<<"["
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._x<<", "
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._y<<", "
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._z<<"]";
  return output;
} 

The code allows to print a vector as std::cout<<v<<std::endl;. Each number has 23 spaces, of which 16 are the decimals. The text is right-aligned so that it will print:

 1.123456123456e+01
-1.123456123456e+01

Instead of

1.123456123456e+01
-1.123456123456e+01

The code seems awfully repetitive. How can you "store" the format (all the setiosflags, setw and setprecision statements) such that you can say something like "print the characters in a standard way, but the numbers with this given format".

Thank you!

Edit

As per Rob Adams' comment, I changed my ugly code (which, as pointed out by others, would mess up the precision for the "next guy") to a more succinct (and correct):

std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  output<<"["
    <<std::setw(23)<<v._x<<", "
    <<std::setw(23)<<v._y<<", "
    <<std::setw(23)<<v._z
    <<"]";
  output.flags(f);
  output.precision(p);
  return output;
}
like image 339
Escualo Avatar asked Mar 16 '11 16:03

Escualo


People also ask

What is the use of Iomanip?

The iomanip is a library in C++ which helps us in manipulating the output of any C++ program. There are many functions in this library that help in manipulating the output. To name a few we have functions to reset flags, set fill characters, set precision, get date and time, etc.

Why should we include Iomanip header file in the program?

The header <iomanip> contains the functions that we can use to format the output of the C++ program. These functions can be used one at a time or together to make the output of our program more presentable.

What is the Iomanip header in C++?

The header <iomanip> is part of the Input/output library of the C++ Standard Library. It defines the manipulator functions resetiosflags() , setiosflags() , setbase() , setfill() , setprecision() , and setw() . These functions may be conveniently used by C++ programs to affect the state of iostream objects.

What does Iomanip stand for?

iomanip: iomanip stands for input-output manipulators. The methods declared in these files are used for manipulating streams.


2 Answers

Only std::setw() is temporary. The other two calls, setiosflags, and setprecision have a permanent effect.

So, you could change your code to :

std::ostream& operator<<(std::ostream &output, const Vector &v){
  output<<"["
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._x<<", "
    <<std::setw(23)
    <<v._y<<", "
    <<std::setw(23)
    <<v._z<<"]";
  return output;
} 

But now you've borked the flags and precision for the next guy. Try this instead:

std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  output<<"["
    <<std::setw(23)
    <<v._x<<", "
    <<std::setw(23)
    <<v._y<<", "
    <<std::setw(23)
    <<v._z<<"]";
  output.flags(f);
  output.precision(p);
  return output;
} 

Finally, if you absolutely have to get rid of the duplication of the constant 23, you could do something like this (but I wouldn't recommend it):

struct width {
  int w;
  width(int w) : w(w) {}
  friend std::ostream& operator<<(std::ostream&os, const width& w) {
    return os << std::setw(width.w);
  }
};


std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  width w(23);
  output<<"["
    <<w
    <<v._x<<", "
    <<w
    <<v._y<<", "
    <<w
    <<v._z<<"]";
  output.flags(f);
  output.precision(p);
  return output;
} 

See also this other question, where they decided that you can't make width permanent.

like image 174
Robᵩ Avatar answered Sep 27 '22 19:09

Robᵩ


In C++20 you'll be able to do:

std::ostream& operator<<(std::ostream& output, const Vector& v){
  const int width = 23, precision = 16;
  return output << std::format(
      "[{0:{3}.{4}e}, {1:{3}.{4}e}, {2:{3}.{4}e}]",
      v._x, v._y, v._z, width, precision);
} 

Unlike I/O manipulators, std::format will not change ostream's formatting state saving your from the problem mentioned by Bo Persson:

Your real problem is what happens to the next output after this one...

It will also work I/O manipulators work correctly with Vector.

Until std::format is available you can use the {fmt} library it is based on.

like image 29
vitaut Avatar answered Sep 27 '22 19:09

vitaut