Search code examples
c++stdoutcursor-position

how to define my own special character in cout


for example :

cout << "  hello\n400";

will print:

  hello
400

another example:

cout << "  hello\r400";

will print:

400ello

there is a option to define my own special character? i would like to make somthing like:

cout << "  hello\d400";

would give:

  hello
  400

(/d is my special character, and i already got the function to get the stdout cursor one line down(cursorDown()),but i just don't how to define a special character that each time will be writted will call to my cursorDown() function)


Solution

  • There is no way to define "new" special characters.

    But you can make the stream interpret specific characters to have new meanings (that you can define). You can do this using locals.

    Some things to note:

    The characters in the string "xyza" is just a simple way of encoding a string. Escaped characters are C++ way of allowing you to represent representing characters that are not visible but are well defined. Have a look at an ASCII table and you will see that all characters in the range 00 -> 31 (decimal) have special meanings (often referred to as control characters).

    See Here: http://www.asciitable.com/

    You can place any character into a string by using the escape sequence to specify its exact value; i.e. \x0A used in a string puts the "New Line" character in the string.

    The more commonly used "control characters" have shorthand versions (defined by the C++ language). '\n' => '\x0A' but you can not add new special shorthand characters as this is just a convenience supply by the language (its like a tradition that most languages support).

    But given a character can you give it a special meaning in an IO stream. YES. You need to define a facet for a locale then apply that locale to the stream.

    Note: Now there is a problem with applying locals to std::cin/std::out. If the stream has already been used (in any way) applying a local may fail and the OS may do stuff with the stream before you reach main() and thus applying a locale to std::cin/std::cout may fail (but you can do it to file and string streams easily).

    So how do we do it.

    Lets use "Vertical Tab" as the character we want to change the meaning of. I pick this as there is a shortcut for it \v (so its shorter to type than \x0B) and usually has no meaning for terminals.

    Lets define the meaning as new line and indent 3 spaces.

    #include <locale>
    #include <algorithm>
    #include <iostream>
    #include <fstream>
    
    class IndentFacet: public std::codecvt<char,char,std::mbstate_t>
    {
      public:
       explicit IndentFacet(size_t ref = 0): std::codecvt<char,char,std::mbstate_t>(ref)    {}  
    
        typedef std::codecvt_base::result               result;
        typedef std::codecvt<char,char,std::mbstate_t>  parent;
        typedef parent::intern_type                     intern_type;
        typedef parent::extern_type                     extern_type;
        typedef parent::state_type                      state_type;
    
      protected:
        virtual result do_out(state_type& tabNeeded,
                             const intern_type* rStart, const intern_type*  rEnd, const intern_type*&   rNewStart,
                             extern_type*       wStart, extern_type*        wEnd, extern_type*&         wNewStart) const
        {   
            result  res = std::codecvt_base::ok;
    
            for(;(rStart < rEnd) && (wStart < wEnd);++rStart,++wStart)
            {   
                if (*rStart == '\v')
                {   
                    if (wEnd - wStart < 4)
                    {   
                        // We do not have enough space to convert the '\v`
                        // So stop converting and a subsequent call should do it.
                        res = std::codecvt_base::partial;
                        break;
                    }   
                    // if we find the special character add a new line and three spaces
                    wStart[0] = '\n';
                    wStart[1] = ' ';
                    wStart[2] = ' ';
                    wStart[3] = ' ';
    
                    // Note we do +1 in the for() loop
                    wStart += 3;
                }   
                else
                {
                    // Otherwise just copy the character.
                    *wStart             = *rStart;
                }   
            }   
    
            // Update the read and write points.
            rNewStart   = rStart;
            wNewStart   = wStart;
    
            // return the appropriate result.
            return res;
        }   
    
        // Override so the do_out() virtual function is called.
        virtual bool do_always_noconv() const throw()
        {   
            return false;   // Sometime we add extra tabs
        }   
    
    };
    

    Some code that uses the locale.

    int main()
    {
        std::ios::sync_with_stdio(false);
    
        /* Imbue std::cout before it is used */
        std::cout.imbue(std::locale(std::locale::classic(), new IndentFacet()));
    
        // Notice the use of '\v' after the first lien
        std::cout << "Line 1\vLine 2\nLine 3\n";
    
        /* You must imbue a file stream before it is opened. */
        std::ofstream       data;
        data.imbue(std::locale(std::locale::classic(), new IndentFacet()));
        data.open("PLOP");
    
        // Notice the use of '\v' after the first lien
        data << "Loki\vUses Locale\nTo do something silly\n";
    }
    

    The output:

    > ./a.out
    Line 1
       Line 2
    Line 3
    > cat PLOP
    Loki
       Uses Locale
    To do something silly
    

    BUT

    Now writing all this is not really worth it. If you want a fixed indent like that us a named variable that has those specific characters in it. It makes your code slightly more verbose but does the trick.

    #include <string>
    #include <iostream>
    
    std::string const newLineWithIndent = "\n   ";
    
    int main()
    {
        std::cout << "  hello" << newLineWithIndent << "400";
    }