Egor Chubarov Egor Chubarov - 2 months ago 37
C++ Question

C++ setw() not working as expected

I need to print some data on in the console. My code is:

cout << setw(5) << left << "id" << " | " << setw(10) << left << "computer" << " | " << setw(11) << left << "subsystem" << " | " <<
setw(8) << left << "number" << " | " << setw(80) << left << "name" << " | " << setw(13) << left << "config_file" << endl;
for (int i = 0; i < rows; i++)
{
cout << setw(5) << left << subsystem_table_data[i].id << " | " << setw(10) << left << subsystem_table_data[i].computer << " | " <<
setw(11) << left << subsystem_table_data[i].subsystem << " | " << setw(8) << left << subsystem_table_data[i].number << " | " <<
setw(80) << left << subsystem_table_data[i].name << " | " << setw(13) << left << subsystem_table_data[i].config_file << endl;
}


Output (scroll right to see):

id | computer | subsystem | number | name | config_file
1 | 1 | 2 | 0 | Computer 1 - Общая компьютерная платформа - 1 | 1
2 | 1 | 1 | 0 | Computer 1 - Launcher - 1 | 2
3 | 1 | 23 | 0 | Computer 1 - Дисплей - 1 | 3
4 | 1 | 11 | 0 | Computer 1 - Контроллер цифровой - 1 | 4
5 | 1 | 21 | 0 | Computer 1 - Отладки - 1 | 5
6 | 2 | 2 | 0 | Computer 2 - Общая компьютерная платформа - 1 | 6
7 | 2 | 1 | 0 | Computer 2 - Launcher - 1 | 7
8 | 2 | 23 | 0 | Computer 2 - Дисплей - 1 | 8


Expected output (on the right again):

id | computer | subsystem | number | name | config_file
1 | 1 | 2 | 0 | Computer 1 - Общая компьютерная платформа - 1 | 1
2 | 1 | 1 | 0 | Computer 1 - Launcher - 1 | 2
3 | 1 | 23 | 0 | Computer 1 - Дисплей - 1 | 3
4 | 1 | 11 | 0 | Computer 1 - Контроллер цифровой - 1 | 4
5 | 1 | 21 | 0 | Computer 1 - Отладки - 1 | 5
6 | 2 | 2 | 0 | Computer 2 - Общая компьютерная платформа - 1 | 6
7 | 2 | 1 | 0 | Computer 2 - Launcher - 1 | 7
8 | 2 | 23 | 0 | Computer 2 - Дисплей - 1 | 8


I suggest there is something wrong with
setw(80) << left << subsystem_table_data[i].name
code part, but can't seem to find the problem. And as far as I know, this is not because of the total console width, since the first line is printed just fine.

Answer

setw() works as designed. It pads the output to given number of bytes1.

The problem is that you don't need number of bytes, you need text width, but unless your text is purely ASCII and printed with monospace font, these two things are different.

The differences occur at several levels:

  1. Unicode code-points above the ASCII range are encoded (assuming UTF-8; it looks like the output is in UTF-8 from the number of bytes the various lines are taking) as muliple bytes.
  2. Multiple code-points may combine to a single glyph. If you are using composed normal form, which is the standard except for MacOS filesystem, all Cyrillic glyphs are single code-point, but in decomposed form, 'й' would be two.
  3. Glyphs may take different screen width.

The first point is what is causing your misalignment, possibly combined with the second if decomposed characters may appear in your input. As long as you output using monospace font, the third is not a concern for Cyrillic, but if there is any chance you might run across some Chinese/Japanese/Korean text, keep in mind that their glyphs are mostly "full-width" and those take space of two Latin or Cyrillic letters on most terminals.

The C++ standard library does not have support for counting glyphs. You'll need to use a unicode support library (like ICU) and handle the alignment yourself—or take the easy way out and make the text column the last, so it's end will not matter.


1 The documentation for operator<<(std::ostream&, std::string const&) describes effect of width() in term of std::string::size() and that is definitely bytes.