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