c++stringstringstreamostreammanipulators

Center text in fixed-width field with stream manipulators in C++


I am refactoring some legacy code which is using printf with longs strings (without any actual formatting) to print out plain text table headers which looks notionally like this:

|  Table   |  Column  | Header  |

which are currently being produced like this:

printf("|  Table   |  Column  | Header  |");

I would like to produce the above with code to the effect of1:

outputStream << "|" << std::setw(10) << std::center << "Table"
             << "|" << std::setw(10) << std::center << "Column"
             << "|" << std::setw(9) << std::center << "Header"
             << "|" << std::endl;

which does not compile because <iomanip> has the stream manipulators std::left, std::right and std::internal, but does not seem to have any std::center. Is there a clean way to do this already in standard C++ libraries, or will I have to manually compute the necessary spacing?


1Even though this is more verbose than the C code, it will be less verbose in the long run because of the number of printf statements and the amount of infixed duplication in their strings. It will also be more extensible and maintainable.


Solution

  • Here's a helper class that accomplish what you want:

    #include <string>
    #include <iostream>
    #include <iomanip>
    
    template<typename charT, typename traits = std::char_traits<charT> >
    class center_helper {
        std::basic_string<charT, traits> str_;
    public:
        center_helper(std::basic_string<charT, traits> str) : str_(str) {}
        template<typename a, typename b>
        friend std::basic_ostream<a, b>& operator<<(std::basic_ostream<a, b>& s, const center_helper<a, b>& c);
    };
    
    template<typename charT, typename traits = std::char_traits<charT> >
    center_helper<charT, traits> centered(std::basic_string<charT, traits> str) {
        return center_helper<charT, traits>(str);
    }
    
    // redeclare for std::string directly so we can support anything that implicitly converts to std::string
    center_helper<std::string::value_type, std::string::traits_type> centered(const std::string& str) {
        return center_helper<std::string::value_type, std::string::traits_type>(str);
    }
    
    template<typename charT, typename traits>
    std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& s, const center_helper<charT, traits>& c) {
        std::streamsize w = s.width();
        if (w > c.str_.length()) {
            std::streamsize left = (w + c.str_.length()) / 2;
            s.width(left);
            s << c.str_;
            s.width(w - left);
            s << "";
        } else {
            s << c.str_;
        }
        return s;
    }
    

    It's used simply by calling centered("String"), like so:

    int main(int argc, char *argv[]) {
        std::cout << "|" << std::setw(10) << centered("Table")
                  << "|" << std::setw(10) << centered("Column")
                  << "|" << std::setw(9)  << centered("Header") << "|"
                  << std::endl;
    }