3
\$\begingroup\$

Previously asked here.

The code is now available on GitHub.

Since the previous review I have added unit tests.

Since it is big it will come in a couple of parts.

Part 1 | Part 2 | Part 3 | Part 4

Part 4

Printing strings and integers did not work the same way on streams as they did in printf(). So I had to write some special handling code to deal with these types to get the unit tests to all work.

printToStream.h

#ifndef THORSANVIL_IOUTIL_PRINT_TO_STREAM_H
#define THORSANVIL_IOUTIL_PRINT_TO_STREAM_H

#include "FormatInfo.h"
#include "printIntToStream.h"
#include "printStringToStream.h"

#include <ostream>

namespace ThorsAnvil::IOUtil
{

template<typename T>
inline void printToStreamDefault(std::ostream& s, T const& arg, FormatInfo const&)
{
    s << arg;
}

/* Template method for everything apart from integers */
template<typename T>
inline
typename std::enable_if<!std::is_integral<T>::value>::type
printToStream(std::ostream& s, T const& arg, FormatInfo const& info)
{
    // Just use the standard stream printing methods.
    // Below we special case Int and string handling (floats work as expected).
    printToStreamDefault(s, arg, info);
}

/*
 * As a char is an integer we need to detect this case
 * and call the standard printing functions for Type::Char
 *
 * But we can also print an integer using a char in which case we need to convert the char to an integer
 * before we can print it. Below are helper functions to convert Char to Int when printing a char as an
 * an integer.
 */
template<typename T>
struct CharIntConverter
{
    using Integer   = T;
};
template<>
struct CharIntConverter<char>
{
    using Integer   = int;
};
template<>
struct CharIntConverter<unsigned char>
{
    using Integer   = unsigned int;
};


template<typename T>
inline
typename std::enable_if<std::is_integral<T>::value>::type
printToStream(std::ostream& s, T const& arg, FormatInfo const& info)
{
    if (info.type == Type::Char)
    {
        printToStreamDefault(s, static_cast<char>(arg), info);
    }
    else
    {
        printIntToStream(s, static_cast<typename CharIntConverter<T>::Integer>(arg), info);
    }
}

// C-String
inline void printToStream(std::ostream& s, char const* const& arg, FormatInfo const& info)
{
    printStringToStream(s, arg, info);
}


}

#endif

PrintStringToStream.h

#ifndef THORSANVIL_IOUTIL_PRINT_STRING_TO_STREAM_H
#define THORSANVIL_IOUTIL_PRINT_STRING_TO_STREAM_H

#include "FormatInfo.h"
#include "printToStream.h"

namespace ThorsAnvil::IOUtil
{

inline void printStringToStream(std::ostream& s, char const* const& arg, FormatInfo const& info)
{
    if (info.precision == -1)
    {
        s << arg;
    }
    else
    {
        s.width(0);
        std::size_t     padding = (info.precision >= info.width) ? 0 :  (info.width - info.precision);
        if (!info.leftJustify)
        {
            for (std::size_t loop = 0; loop < padding; ++loop)
            {
                s.put(' ');
            }
        }
        for (std::size_t loop = 0; arg[loop] != '\0' && loop < info.precision; ++loop)
        {
            s.put(arg[loop]);
        }
        if (info.leftJustify)
        {
            for (std::size_t loop = 0; loop < padding; ++loop)
            {
                s.put(' ');
            }
        }
    }
}

}

PrintIntToStream

#ifndef THORSANVIL_IOUTIL_PRINT_INT_TO_STREAM_H
#define THORSANVIL_IOUTIL_PRINT_INT_TO_STREAM_H

#include "FormatInfo.h"
#include "printToStream.h"

#include <ostream>
#include <cmath>

namespace ThorsAnvil::IOUtil
{

inline long long            absm(long long arg)            {return std::abs(arg);}
inline long                 absm(long arg)                 {return std::abs(arg);}
inline int                  absm(int arg)                  {return std::abs(arg);}
inline unsigned long long   absm(unsigned long long arg)   {return arg;}
inline unsigned long        absm(unsigned long arg)        {return arg;}
inline unsigned int         absm(unsigned int arg)         {return arg;}

template<typename T>
inline void printIntToStream(std::ostream& s, T arg, FormatInfo const& info)
{
    static long double  const logFor16    = std::log10(16.0L);
    static long double  const logFor10    = std::log10(10.0L);
    static long double  const logFor08    = std::log10(8.0L);

    double const&  logBase = s.flags() & std::ios_base::oct ? logFor08 : s.flags() & std::ios_base::hex ? logFor16 : logFor10;

    if (info.width == 0 && info.precision == -1)
    {
        s << arg;
    }
    else
    {
        std::size_t width     = info.width;
        std::size_t precision = info.precision;

        /*
         * When precision or Width are specified the default does not do the same as C sprintf library
         * So we are going to take care of it manually here
         * So turn off the standard printing functions
         */
        s.width(0);
        s.unsetf(std::ios_base::showpos | std::ios_base::showbase);

        // extraChar    extra charters we are forced to print.  +- 0x
        // extraDigits  extra digits we are forced to print prefix 0 for octal numbers
        std::size_t extraChar       = (arg < 0) || (arg >=0 && info.forceSign && info.type == Type::Int) ? 1 : 0;
        std::size_t extraDigits     = 0;

        if (info.prefixType)
        {
            switch (s.flags() & std::ios_base::basefield)
            {
                case std::ios_base::hex:    extraChar   += 2;break;
                case std::ios_base::oct:    extraDigits += 1;break;
            }
        }
        /*
         * Number of digits to print
         * If arg is not zero use logs to calculate the number of digits.
         * If it is zero then the size is 1 unless the precision is zero (a zero value with zero precision prints nothing)
         */
        width                      = extraChar  > width ? 0 : width - extraChar;
        std::size_t numberOfDigits = (arg != 0 ? static_cast<int>((std::log10(static_cast<long double>(absm(arg))) / logBase + 1)) : (precision == 0 ? 0 : 1)) + extraDigits;
        std::size_t sizeOfNumber   = precision == -1 || numberOfDigits > precision ? numberOfDigits : precision;
        std::size_t prefix         = precision == -1 || numberOfDigits > precision ? 0 : (precision - numberOfDigits);
        std::size_t padding        = (sizeOfNumber >= width) ? 0 :  (width - sizeOfNumber);
        if (precision == -1 && info.leftPad && !info.leftJustify)
        {
            std::swap(prefix, padding);
        }

        // Add spaces before number to make it fit in width.
        if (!info.leftJustify)
        {
            for (std::size_t loop = 0; loop < padding; ++loop)
            {
                s.put(' ');
            }
        }
        // Add the - or + sign if required.
        if (arg < 0)
        {
            s.put('-');
        }
        else if (arg >=0 && info.forceSign && info.type == Type::Int)
        {
            s.put('+');
        }

        // Add the Octal or Hex prefix
        if (info.prefixType)
        {
            if (s.flags() & (std::ios_base::hex | std::ios_base::oct))
            {
                s.put('0');
            }
            if (s.flags() & std::ios_base::hex)
            {
                s.put(s.flags() & std::ios_base::uppercase ? 'X' : 'x');
            }
        }

        // Add any prefix 0 needed.
        for (std::size_t loop = 0; loop < prefix; ++loop)
        {
            s.put('0');
        }

        // Print out the absolute value (we have already printed the sign)
        // Don't print anything if precision is 0 and value is 0
        if (precision != 0 || arg != 0)
        {
            s << absm(arg);
        }
        // Add spaces after number to make it fit in width.
        if (info.leftJustify)
        {
            for (std::size_t loop = 0; loop < padding; ++loop)
            {
                s.put(' ');
            }
        }
    }
}

}

#endif
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.