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
Phrancis
20.5k6 gold badges69 silver badges155 bronze badges
asked Mar 4, 2018 at 19:22
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.