6
\$\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\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.