C++ string Formatter Again Part-3

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
4
down vote
favorite
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 3
This is where all the dirty work is handled. When apply() is called this object formats the object according to the info (FormatInfo) on the stream.
In apply() we do some basic type checking and allow some simple conversions on the input parameters. Note in C (char, short) are converted to int when passed as parameters so some hacking around that is done to compensate in C++.
After allowed conversions have been applied the appropriate stream formatter functions are called to put the stream into the correct state, then we call printToStream(), after this completes the stream is reset to its original state.
Note: You can think of printToStream() as s << arg;
In part 4 you will see how this is not quite true.
Formatter.h
#ifndef THORSANVIL_IOUTIL_FORMATTER_H
#define THORSANVIL_IOUTIL_FORMATTER_H
#include "printToStream.h"
#include "saveToStream.h"
#include "FormatInfo.h"
#include "SignConversionOption.h"
#include <ostream>
#include <string>
#include <map>
#include <exception>
#include <stdexcept>
#include <typeindex>
#include <cassert>
#include <type_traits>
namespace ThorsAnvil::IOUtil
template<typename T>
inline bool checkNumLargerEqualToZero(T const& value) return value >= 0;
inline bool checkNumLargerEqualToZero(char const*) return false;
class Formatter
// The number of characters read in the formatter.
std::size_t used;
// If this object reads the width/precision from the next parameter
// If this value is Dynamic::Width or Dynamic::Precision then info is not used.
Dynamic dynamicSize;
// Details extracted from the format string.
FormatInfo info;
// When you apply a `Formatter` object to a stream this temporary object is created.
// When the actual object is then applied to this object we call back to the Formatter::apply()
// method to do the actual work of setting up the stream and printing the value. When it is
// all done we return the original stream.
// see below friend FormatterCheck operator<<(std::ostream&, Format const&)
// Usage:
// stream << FormatObject << value;
// Notes:
// stream << FormatObject returns a FormatChecker
// FormatChecker << value calls apply()
// the returns the original stream
struct FormatterCheck
std::ostream& stream;
Formatter const& formatter;
FormatterCheck(std::ostream& s, Formatter const& formatter)
: stream(s)
, formatter(formatter)
template<typename A>
std::ostream& operator<<(A const& nextArg)
formatter.apply(stream, nextArg);
return stream;
;
public:
/* The constructor reads a string
* and sets up all the data into info
* Unless we find a Dynamic Width/Precision
* then dynamicSize is updated and we return immediately indicating zero size.
* The Format constructor will then call again to get the actual formatter object.
*/
Formatter(char const* formatStr, Dynamic dynamicWidthHandeled)
: used(0)
, dynamicSize(Dynamic::None)
, info()
info.specifier == Specifier::F)
= std::ios_base::fixed;
else if (info.specifier == Specifier::e
std::size_t size() const return used;
Dynamic isDynamicSize() const return dynamicSize;
// We pass the formatter to the stream first
// So we create a marker object used to print the actual argument.
// This will call apply() with the actual argument.
friend FormatterCheck operator<<(std::ostream& s, Formatter const& formatter)
return FormatterCheck(s, formatter);
private:
template<typename A>
void apply(std::ostream& s, A const& arg) const
if (dynamicSize == Dynamic::None)
using Actual = typename SignConversionOption<A>::Actual;
using Alternative = typename SignConversionOption<A>::Alternative;
if (std::type_index(typeid(Actual)) == std::type_index(*info.expectedType.first))
applyData(s, arg);
else if (std::type_index(typeid(Actual)) != std::type_index(typeid(Alternative)) && std::type_index(*info.expectedType.first) == std::type_index(typeid(Alternative)))
applyData(s, static_cast<Alternative const&>(arg));
else if (SignConversionOption<A>::allowIntConversion)
applyData(s, SignConversionOption<A>::convertToInt(arg));
else if (std::type_index(typeid(A)) == std::type_index(typeid(int)) && info.expectedType.second)
applyData(s, SignConversionOption<A>::truncate(arg, info.expectedType.second));
else
throw std::invalid_argument(std::string("Actual argument does not match supplied argument (or conversions): Expected(") + info.expectedType.first->name() + ") Got(" + typeid(A).name() + ")");
else
if (std::type_index(typeid(A)) != std::type_index(typeid(int)))
throw std::invalid_argument("Dynamic Width of Precision is not an int");
saveToStream(s, dynamicSize, arg);
template<typename A>
void applyData(std::ostream& s, A const& arg) const
// Only certain combinations of Specifier and Length are supported.
static AllowedType getType(Length length, Type type)
static std::map<std::pair<Type, Length>, AllowedType> typeMap =
#pragma vera-pushoff
Type::Int, Length::none, &typeid(int), 0,
Type::Int, Length::hh, &typeid(signed char), 0xFF,
Type::Int, Length::h, &typeid(short int), 0xFFFF,
Type::Int, Length::l, &typeid(long int), 0,
Type::Int, Length::ll, &typeid(long long int), 0,
Type::Int, Length::j, &typeid(std::intmax_t), 0,
Type::Int, Length::z, &typeid(std::size_t), 0,
Type::Int, Length::t, &typeid(std::ptrdiff_t), 0,
Type::UInt, Length::none, &typeid(unsigned int), 0,
Type::UInt, Length::hh, &typeid(unsigned char), 0xFF,
Type::UInt, Length::h, &typeid(unsigned short int), 0xFFFF,
Type::UInt, Length::l, &typeid(unsigned long int), 0,
Type::UInt, Length::ll, &typeid(unsigned long long int), 0,
Type::UInt, Length::j, &typeid(std::intmax_t), 0,
Type::UInt, Length::z, &typeid(std::size_t), 0,
Type::UInt, Length::t, &typeid(std::ptrdiff_t), 0,
Type::Float, Length::none, &typeid(double), 0, Type::Float, Length::l, &typeid(double), 0, Type::Float, Length::L, &typeid(long double), 0,
Type::Char, Length::none, &typeid(int), 0, Type::Char, Length::l, &typeid(std::wint_t), 0,
Type::String,Length::none, &typeid(char const*), 0, Type::String,Length::l, &typeid(wchar_t const*), 0,
Type::Pointer,Length::none,&typeid(void*), 0,
Type::Count, Length::none, &typeid(int*), 0,
Type::Count, Length::hh, &typeid(signed char*), 0,
Type::Count, Length::h, &typeid(short int*), 0,
Type::Count, Length::l, &typeid(long int*), 0,
Type::Count, Length::ll, &typeid(long long int*), 0,
Type::Count, Length::j, &typeid(std::intmax_t*), 0,
Type::Count, Length::z, &typeid(std::size_t*), 0,
Type::Count, Length::t, &typeid(std::ptrdiff_t*), 0
#pragma vera-pop
;
auto find = typeMap.find(type, length);
if (find == typeMap.end())
throw std::invalid_argument("Specifier and length are not a valid combination");
return find->second;
;
#endif
SignConversionOption
#ifndef THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
#define THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
namespace ThorsAnvil::IOUtil
/*
* When handling integer types some
* automatic conversions are allowed.
*
* This type handles these conversions.
* It is used by Formatter::apply()
*/
template<typename T>
struct SignConversionOption
using Actual = T; // The Current Type
using Alternative = T; // Acceptable alternative type we can cast from
static constexpr bool allowIntConversion = false; // Can we convert this type from int by call convertToInt()
static int convertToInt(T const&) return 0;
static int truncate(T const& arg, int mask) return 0;; // Int only we truncate the value by masking if top bits.
// The mask is retrieved from Formatter::getType()
;
template<>
struct SignConversionOption<char>
using Actual = char;
using Alternative = unsigned char;
static constexpr bool allowIntConversion = true;
static int convertToInt(char const& arg) return arg;
static int truncate(char const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<short>
using Actual = short;
using Alternative = unsigned short;
static constexpr bool allowIntConversion = true;
static int convertToInt(short const& arg) return arg;
static int truncate(short const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<int>
using Actual = int;
using Alternative = unsigned int;
static constexpr bool allowIntConversion = false;
static int convertToInt(int const&) return 0;
static int truncate(int const& arg, int mask) return arg & mask;;
;
template<>
struct SignConversionOption<long>
using Actual = long;
using Alternative = unsigned long;
static constexpr bool allowIntConversion = false;
static int convertToInt(long const&) return 0;
static int truncate(long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<long long>
using Actual = long long;
using Alternative = unsigned long long;
static constexpr bool allowIntConversion = false;
static int convertToInt(long long const&) return 0;
static int truncate(long long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned char>
using Actual = unsigned char;
using Alternative = char;
static constexpr bool allowIntConversion = true;
static int convertToInt(unsigned char const& arg) return arg;
static int truncate(unsigned char const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned short>
using Actual = unsigned short;
using Alternative = short;
static constexpr bool allowIntConversion = true;
static int convertToInt(unsigned short const& arg) return arg;
static int truncate(unsigned short const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned int>
using Actual = unsigned int;
using Alternative = int;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned int const&) return 0;
static int truncate(unsigned int const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned long>
using Actual = unsigned long;
using Alternative = long;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned long const&) return 0;
static int truncate(unsigned long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned long long>
using Actual = unsigned long long;
using Alternative = long long;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned long long const&) return 0;
static int truncate(unsigned long long const& arg, int mask) return 0;;
;
#endif
SaveToStream
#ifndef THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#define THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#include "FormatInfo.h"
#include <iostream>
namespace ThorsAnvil::IOUtil
template<typename T>
inline void saveToStream(std::ostream&, Dynamic, T const&)
template<>
inline void saveToStream(std::ostream& s, Dynamic pos, int const& size)
s.iword(static_cast<int>(pos)) = size;
#endif
c++ formatting serialization stream
 |Â
show 2 more comments
up vote
4
down vote
favorite
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 3
This is where all the dirty work is handled. When apply() is called this object formats the object according to the info (FormatInfo) on the stream.
In apply() we do some basic type checking and allow some simple conversions on the input parameters. Note in C (char, short) are converted to int when passed as parameters so some hacking around that is done to compensate in C++.
After allowed conversions have been applied the appropriate stream formatter functions are called to put the stream into the correct state, then we call printToStream(), after this completes the stream is reset to its original state.
Note: You can think of printToStream() as s << arg;
In part 4 you will see how this is not quite true.
Formatter.h
#ifndef THORSANVIL_IOUTIL_FORMATTER_H
#define THORSANVIL_IOUTIL_FORMATTER_H
#include "printToStream.h"
#include "saveToStream.h"
#include "FormatInfo.h"
#include "SignConversionOption.h"
#include <ostream>
#include <string>
#include <map>
#include <exception>
#include <stdexcept>
#include <typeindex>
#include <cassert>
#include <type_traits>
namespace ThorsAnvil::IOUtil
template<typename T>
inline bool checkNumLargerEqualToZero(T const& value) return value >= 0;
inline bool checkNumLargerEqualToZero(char const*) return false;
class Formatter
// The number of characters read in the formatter.
std::size_t used;
// If this object reads the width/precision from the next parameter
// If this value is Dynamic::Width or Dynamic::Precision then info is not used.
Dynamic dynamicSize;
// Details extracted from the format string.
FormatInfo info;
// When you apply a `Formatter` object to a stream this temporary object is created.
// When the actual object is then applied to this object we call back to the Formatter::apply()
// method to do the actual work of setting up the stream and printing the value. When it is
// all done we return the original stream.
// see below friend FormatterCheck operator<<(std::ostream&, Format const&)
// Usage:
// stream << FormatObject << value;
// Notes:
// stream << FormatObject returns a FormatChecker
// FormatChecker << value calls apply()
// the returns the original stream
struct FormatterCheck
std::ostream& stream;
Formatter const& formatter;
FormatterCheck(std::ostream& s, Formatter const& formatter)
: stream(s)
, formatter(formatter)
template<typename A>
std::ostream& operator<<(A const& nextArg)
formatter.apply(stream, nextArg);
return stream;
;
public:
/* The constructor reads a string
* and sets up all the data into info
* Unless we find a Dynamic Width/Precision
* then dynamicSize is updated and we return immediately indicating zero size.
* The Format constructor will then call again to get the actual formatter object.
*/
Formatter(char const* formatStr, Dynamic dynamicWidthHandeled)
: used(0)
, dynamicSize(Dynamic::None)
, info()
info.specifier == Specifier::F)
= std::ios_base::fixed;
else if (info.specifier == Specifier::e
std::size_t size() const return used;
Dynamic isDynamicSize() const return dynamicSize;
// We pass the formatter to the stream first
// So we create a marker object used to print the actual argument.
// This will call apply() with the actual argument.
friend FormatterCheck operator<<(std::ostream& s, Formatter const& formatter)
return FormatterCheck(s, formatter);
private:
template<typename A>
void apply(std::ostream& s, A const& arg) const
if (dynamicSize == Dynamic::None)
using Actual = typename SignConversionOption<A>::Actual;
using Alternative = typename SignConversionOption<A>::Alternative;
if (std::type_index(typeid(Actual)) == std::type_index(*info.expectedType.first))
applyData(s, arg);
else if (std::type_index(typeid(Actual)) != std::type_index(typeid(Alternative)) && std::type_index(*info.expectedType.first) == std::type_index(typeid(Alternative)))
applyData(s, static_cast<Alternative const&>(arg));
else if (SignConversionOption<A>::allowIntConversion)
applyData(s, SignConversionOption<A>::convertToInt(arg));
else if (std::type_index(typeid(A)) == std::type_index(typeid(int)) && info.expectedType.second)
applyData(s, SignConversionOption<A>::truncate(arg, info.expectedType.second));
else
throw std::invalid_argument(std::string("Actual argument does not match supplied argument (or conversions): Expected(") + info.expectedType.first->name() + ") Got(" + typeid(A).name() + ")");
else
if (std::type_index(typeid(A)) != std::type_index(typeid(int)))
throw std::invalid_argument("Dynamic Width of Precision is not an int");
saveToStream(s, dynamicSize, arg);
template<typename A>
void applyData(std::ostream& s, A const& arg) const
// Only certain combinations of Specifier and Length are supported.
static AllowedType getType(Length length, Type type)
static std::map<std::pair<Type, Length>, AllowedType> typeMap =
#pragma vera-pushoff
Type::Int, Length::none, &typeid(int), 0,
Type::Int, Length::hh, &typeid(signed char), 0xFF,
Type::Int, Length::h, &typeid(short int), 0xFFFF,
Type::Int, Length::l, &typeid(long int), 0,
Type::Int, Length::ll, &typeid(long long int), 0,
Type::Int, Length::j, &typeid(std::intmax_t), 0,
Type::Int, Length::z, &typeid(std::size_t), 0,
Type::Int, Length::t, &typeid(std::ptrdiff_t), 0,
Type::UInt, Length::none, &typeid(unsigned int), 0,
Type::UInt, Length::hh, &typeid(unsigned char), 0xFF,
Type::UInt, Length::h, &typeid(unsigned short int), 0xFFFF,
Type::UInt, Length::l, &typeid(unsigned long int), 0,
Type::UInt, Length::ll, &typeid(unsigned long long int), 0,
Type::UInt, Length::j, &typeid(std::intmax_t), 0,
Type::UInt, Length::z, &typeid(std::size_t), 0,
Type::UInt, Length::t, &typeid(std::ptrdiff_t), 0,
Type::Float, Length::none, &typeid(double), 0, Type::Float, Length::l, &typeid(double), 0, Type::Float, Length::L, &typeid(long double), 0,
Type::Char, Length::none, &typeid(int), 0, Type::Char, Length::l, &typeid(std::wint_t), 0,
Type::String,Length::none, &typeid(char const*), 0, Type::String,Length::l, &typeid(wchar_t const*), 0,
Type::Pointer,Length::none,&typeid(void*), 0,
Type::Count, Length::none, &typeid(int*), 0,
Type::Count, Length::hh, &typeid(signed char*), 0,
Type::Count, Length::h, &typeid(short int*), 0,
Type::Count, Length::l, &typeid(long int*), 0,
Type::Count, Length::ll, &typeid(long long int*), 0,
Type::Count, Length::j, &typeid(std::intmax_t*), 0,
Type::Count, Length::z, &typeid(std::size_t*), 0,
Type::Count, Length::t, &typeid(std::ptrdiff_t*), 0
#pragma vera-pop
;
auto find = typeMap.find(type, length);
if (find == typeMap.end())
throw std::invalid_argument("Specifier and length are not a valid combination");
return find->second;
;
#endif
SignConversionOption
#ifndef THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
#define THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
namespace ThorsAnvil::IOUtil
/*
* When handling integer types some
* automatic conversions are allowed.
*
* This type handles these conversions.
* It is used by Formatter::apply()
*/
template<typename T>
struct SignConversionOption
using Actual = T; // The Current Type
using Alternative = T; // Acceptable alternative type we can cast from
static constexpr bool allowIntConversion = false; // Can we convert this type from int by call convertToInt()
static int convertToInt(T const&) return 0;
static int truncate(T const& arg, int mask) return 0;; // Int only we truncate the value by masking if top bits.
// The mask is retrieved from Formatter::getType()
;
template<>
struct SignConversionOption<char>
using Actual = char;
using Alternative = unsigned char;
static constexpr bool allowIntConversion = true;
static int convertToInt(char const& arg) return arg;
static int truncate(char const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<short>
using Actual = short;
using Alternative = unsigned short;
static constexpr bool allowIntConversion = true;
static int convertToInt(short const& arg) return arg;
static int truncate(short const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<int>
using Actual = int;
using Alternative = unsigned int;
static constexpr bool allowIntConversion = false;
static int convertToInt(int const&) return 0;
static int truncate(int const& arg, int mask) return arg & mask;;
;
template<>
struct SignConversionOption<long>
using Actual = long;
using Alternative = unsigned long;
static constexpr bool allowIntConversion = false;
static int convertToInt(long const&) return 0;
static int truncate(long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<long long>
using Actual = long long;
using Alternative = unsigned long long;
static constexpr bool allowIntConversion = false;
static int convertToInt(long long const&) return 0;
static int truncate(long long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned char>
using Actual = unsigned char;
using Alternative = char;
static constexpr bool allowIntConversion = true;
static int convertToInt(unsigned char const& arg) return arg;
static int truncate(unsigned char const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned short>
using Actual = unsigned short;
using Alternative = short;
static constexpr bool allowIntConversion = true;
static int convertToInt(unsigned short const& arg) return arg;
static int truncate(unsigned short const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned int>
using Actual = unsigned int;
using Alternative = int;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned int const&) return 0;
static int truncate(unsigned int const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned long>
using Actual = unsigned long;
using Alternative = long;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned long const&) return 0;
static int truncate(unsigned long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned long long>
using Actual = unsigned long long;
using Alternative = long long;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned long long const&) return 0;
static int truncate(unsigned long long const& arg, int mask) return 0;;
;
#endif
SaveToStream
#ifndef THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#define THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#include "FormatInfo.h"
#include <iostream>
namespace ThorsAnvil::IOUtil
template<typename T>
inline void saveToStream(std::ostream&, Dynamic, T const&)
template<>
inline void saveToStream(std::ostream& s, Dynamic pos, int const& size)
s.iword(static_cast<int>(pos)) = size;
#endif
c++ formatting serialization stream
2
soo ... just for the record... what is stopping you from putting all four parts off this thing into one question? And why are you asking these questions in so quick succession?
â Vogel612â¦
Mar 4 at 19:37
2
@Vogel612: Because its big. Putting it all in one would be a huge code review. And we ask people to split up big code reviews. So quickly is because I wrote them all at the same time.
â Martin York
Mar 4 at 20:44
1
Does each part work on its own?
â Raystafarian
Mar 4 at 23:33
@Raystafarian: No. But if I wrote a browser, did you want me to post the whole browser in one code review or logical chunks? Also if you want to compile and test it you can download it from github and build in 4 steps: 1git clone git@github.com:Loki-Astari/ThorsIOUtil.git2cd ThorsIOUtil3./configure4make
â Martin York
Mar 5 at 16:55
No, I was just asking, I can't read that language.
â Raystafarian
Mar 5 at 21:21
 |Â
show 2 more comments
up vote
4
down vote
favorite
up vote
4
down vote
favorite
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 3
This is where all the dirty work is handled. When apply() is called this object formats the object according to the info (FormatInfo) on the stream.
In apply() we do some basic type checking and allow some simple conversions on the input parameters. Note in C (char, short) are converted to int when passed as parameters so some hacking around that is done to compensate in C++.
After allowed conversions have been applied the appropriate stream formatter functions are called to put the stream into the correct state, then we call printToStream(), after this completes the stream is reset to its original state.
Note: You can think of printToStream() as s << arg;
In part 4 you will see how this is not quite true.
Formatter.h
#ifndef THORSANVIL_IOUTIL_FORMATTER_H
#define THORSANVIL_IOUTIL_FORMATTER_H
#include "printToStream.h"
#include "saveToStream.h"
#include "FormatInfo.h"
#include "SignConversionOption.h"
#include <ostream>
#include <string>
#include <map>
#include <exception>
#include <stdexcept>
#include <typeindex>
#include <cassert>
#include <type_traits>
namespace ThorsAnvil::IOUtil
template<typename T>
inline bool checkNumLargerEqualToZero(T const& value) return value >= 0;
inline bool checkNumLargerEqualToZero(char const*) return false;
class Formatter
// The number of characters read in the formatter.
std::size_t used;
// If this object reads the width/precision from the next parameter
// If this value is Dynamic::Width or Dynamic::Precision then info is not used.
Dynamic dynamicSize;
// Details extracted from the format string.
FormatInfo info;
// When you apply a `Formatter` object to a stream this temporary object is created.
// When the actual object is then applied to this object we call back to the Formatter::apply()
// method to do the actual work of setting up the stream and printing the value. When it is
// all done we return the original stream.
// see below friend FormatterCheck operator<<(std::ostream&, Format const&)
// Usage:
// stream << FormatObject << value;
// Notes:
// stream << FormatObject returns a FormatChecker
// FormatChecker << value calls apply()
// the returns the original stream
struct FormatterCheck
std::ostream& stream;
Formatter const& formatter;
FormatterCheck(std::ostream& s, Formatter const& formatter)
: stream(s)
, formatter(formatter)
template<typename A>
std::ostream& operator<<(A const& nextArg)
formatter.apply(stream, nextArg);
return stream;
;
public:
/* The constructor reads a string
* and sets up all the data into info
* Unless we find a Dynamic Width/Precision
* then dynamicSize is updated and we return immediately indicating zero size.
* The Format constructor will then call again to get the actual formatter object.
*/
Formatter(char const* formatStr, Dynamic dynamicWidthHandeled)
: used(0)
, dynamicSize(Dynamic::None)
, info()
info.specifier == Specifier::F)
= std::ios_base::fixed;
else if (info.specifier == Specifier::e
std::size_t size() const return used;
Dynamic isDynamicSize() const return dynamicSize;
// We pass the formatter to the stream first
// So we create a marker object used to print the actual argument.
// This will call apply() with the actual argument.
friend FormatterCheck operator<<(std::ostream& s, Formatter const& formatter)
return FormatterCheck(s, formatter);
private:
template<typename A>
void apply(std::ostream& s, A const& arg) const
if (dynamicSize == Dynamic::None)
using Actual = typename SignConversionOption<A>::Actual;
using Alternative = typename SignConversionOption<A>::Alternative;
if (std::type_index(typeid(Actual)) == std::type_index(*info.expectedType.first))
applyData(s, arg);
else if (std::type_index(typeid(Actual)) != std::type_index(typeid(Alternative)) && std::type_index(*info.expectedType.first) == std::type_index(typeid(Alternative)))
applyData(s, static_cast<Alternative const&>(arg));
else if (SignConversionOption<A>::allowIntConversion)
applyData(s, SignConversionOption<A>::convertToInt(arg));
else if (std::type_index(typeid(A)) == std::type_index(typeid(int)) && info.expectedType.second)
applyData(s, SignConversionOption<A>::truncate(arg, info.expectedType.second));
else
throw std::invalid_argument(std::string("Actual argument does not match supplied argument (or conversions): Expected(") + info.expectedType.first->name() + ") Got(" + typeid(A).name() + ")");
else
if (std::type_index(typeid(A)) != std::type_index(typeid(int)))
throw std::invalid_argument("Dynamic Width of Precision is not an int");
saveToStream(s, dynamicSize, arg);
template<typename A>
void applyData(std::ostream& s, A const& arg) const
// Only certain combinations of Specifier and Length are supported.
static AllowedType getType(Length length, Type type)
static std::map<std::pair<Type, Length>, AllowedType> typeMap =
#pragma vera-pushoff
Type::Int, Length::none, &typeid(int), 0,
Type::Int, Length::hh, &typeid(signed char), 0xFF,
Type::Int, Length::h, &typeid(short int), 0xFFFF,
Type::Int, Length::l, &typeid(long int), 0,
Type::Int, Length::ll, &typeid(long long int), 0,
Type::Int, Length::j, &typeid(std::intmax_t), 0,
Type::Int, Length::z, &typeid(std::size_t), 0,
Type::Int, Length::t, &typeid(std::ptrdiff_t), 0,
Type::UInt, Length::none, &typeid(unsigned int), 0,
Type::UInt, Length::hh, &typeid(unsigned char), 0xFF,
Type::UInt, Length::h, &typeid(unsigned short int), 0xFFFF,
Type::UInt, Length::l, &typeid(unsigned long int), 0,
Type::UInt, Length::ll, &typeid(unsigned long long int), 0,
Type::UInt, Length::j, &typeid(std::intmax_t), 0,
Type::UInt, Length::z, &typeid(std::size_t), 0,
Type::UInt, Length::t, &typeid(std::ptrdiff_t), 0,
Type::Float, Length::none, &typeid(double), 0, Type::Float, Length::l, &typeid(double), 0, Type::Float, Length::L, &typeid(long double), 0,
Type::Char, Length::none, &typeid(int), 0, Type::Char, Length::l, &typeid(std::wint_t), 0,
Type::String,Length::none, &typeid(char const*), 0, Type::String,Length::l, &typeid(wchar_t const*), 0,
Type::Pointer,Length::none,&typeid(void*), 0,
Type::Count, Length::none, &typeid(int*), 0,
Type::Count, Length::hh, &typeid(signed char*), 0,
Type::Count, Length::h, &typeid(short int*), 0,
Type::Count, Length::l, &typeid(long int*), 0,
Type::Count, Length::ll, &typeid(long long int*), 0,
Type::Count, Length::j, &typeid(std::intmax_t*), 0,
Type::Count, Length::z, &typeid(std::size_t*), 0,
Type::Count, Length::t, &typeid(std::ptrdiff_t*), 0
#pragma vera-pop
;
auto find = typeMap.find(type, length);
if (find == typeMap.end())
throw std::invalid_argument("Specifier and length are not a valid combination");
return find->second;
;
#endif
SignConversionOption
#ifndef THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
#define THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
namespace ThorsAnvil::IOUtil
/*
* When handling integer types some
* automatic conversions are allowed.
*
* This type handles these conversions.
* It is used by Formatter::apply()
*/
template<typename T>
struct SignConversionOption
using Actual = T; // The Current Type
using Alternative = T; // Acceptable alternative type we can cast from
static constexpr bool allowIntConversion = false; // Can we convert this type from int by call convertToInt()
static int convertToInt(T const&) return 0;
static int truncate(T const& arg, int mask) return 0;; // Int only we truncate the value by masking if top bits.
// The mask is retrieved from Formatter::getType()
;
template<>
struct SignConversionOption<char>
using Actual = char;
using Alternative = unsigned char;
static constexpr bool allowIntConversion = true;
static int convertToInt(char const& arg) return arg;
static int truncate(char const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<short>
using Actual = short;
using Alternative = unsigned short;
static constexpr bool allowIntConversion = true;
static int convertToInt(short const& arg) return arg;
static int truncate(short const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<int>
using Actual = int;
using Alternative = unsigned int;
static constexpr bool allowIntConversion = false;
static int convertToInt(int const&) return 0;
static int truncate(int const& arg, int mask) return arg & mask;;
;
template<>
struct SignConversionOption<long>
using Actual = long;
using Alternative = unsigned long;
static constexpr bool allowIntConversion = false;
static int convertToInt(long const&) return 0;
static int truncate(long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<long long>
using Actual = long long;
using Alternative = unsigned long long;
static constexpr bool allowIntConversion = false;
static int convertToInt(long long const&) return 0;
static int truncate(long long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned char>
using Actual = unsigned char;
using Alternative = char;
static constexpr bool allowIntConversion = true;
static int convertToInt(unsigned char const& arg) return arg;
static int truncate(unsigned char const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned short>
using Actual = unsigned short;
using Alternative = short;
static constexpr bool allowIntConversion = true;
static int convertToInt(unsigned short const& arg) return arg;
static int truncate(unsigned short const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned int>
using Actual = unsigned int;
using Alternative = int;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned int const&) return 0;
static int truncate(unsigned int const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned long>
using Actual = unsigned long;
using Alternative = long;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned long const&) return 0;
static int truncate(unsigned long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned long long>
using Actual = unsigned long long;
using Alternative = long long;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned long long const&) return 0;
static int truncate(unsigned long long const& arg, int mask) return 0;;
;
#endif
SaveToStream
#ifndef THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#define THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#include "FormatInfo.h"
#include <iostream>
namespace ThorsAnvil::IOUtil
template<typename T>
inline void saveToStream(std::ostream&, Dynamic, T const&)
template<>
inline void saveToStream(std::ostream& s, Dynamic pos, int const& size)
s.iword(static_cast<int>(pos)) = size;
#endif
c++ formatting serialization stream
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 3
This is where all the dirty work is handled. When apply() is called this object formats the object according to the info (FormatInfo) on the stream.
In apply() we do some basic type checking and allow some simple conversions on the input parameters. Note in C (char, short) are converted to int when passed as parameters so some hacking around that is done to compensate in C++.
After allowed conversions have been applied the appropriate stream formatter functions are called to put the stream into the correct state, then we call printToStream(), after this completes the stream is reset to its original state.
Note: You can think of printToStream() as s << arg;
In part 4 you will see how this is not quite true.
Formatter.h
#ifndef THORSANVIL_IOUTIL_FORMATTER_H
#define THORSANVIL_IOUTIL_FORMATTER_H
#include "printToStream.h"
#include "saveToStream.h"
#include "FormatInfo.h"
#include "SignConversionOption.h"
#include <ostream>
#include <string>
#include <map>
#include <exception>
#include <stdexcept>
#include <typeindex>
#include <cassert>
#include <type_traits>
namespace ThorsAnvil::IOUtil
template<typename T>
inline bool checkNumLargerEqualToZero(T const& value) return value >= 0;
inline bool checkNumLargerEqualToZero(char const*) return false;
class Formatter
// The number of characters read in the formatter.
std::size_t used;
// If this object reads the width/precision from the next parameter
// If this value is Dynamic::Width or Dynamic::Precision then info is not used.
Dynamic dynamicSize;
// Details extracted from the format string.
FormatInfo info;
// When you apply a `Formatter` object to a stream this temporary object is created.
// When the actual object is then applied to this object we call back to the Formatter::apply()
// method to do the actual work of setting up the stream and printing the value. When it is
// all done we return the original stream.
// see below friend FormatterCheck operator<<(std::ostream&, Format const&)
// Usage:
// stream << FormatObject << value;
// Notes:
// stream << FormatObject returns a FormatChecker
// FormatChecker << value calls apply()
// the returns the original stream
struct FormatterCheck
std::ostream& stream;
Formatter const& formatter;
FormatterCheck(std::ostream& s, Formatter const& formatter)
: stream(s)
, formatter(formatter)
template<typename A>
std::ostream& operator<<(A const& nextArg)
formatter.apply(stream, nextArg);
return stream;
;
public:
/* The constructor reads a string
* and sets up all the data into info
* Unless we find a Dynamic Width/Precision
* then dynamicSize is updated and we return immediately indicating zero size.
* The Format constructor will then call again to get the actual formatter object.
*/
Formatter(char const* formatStr, Dynamic dynamicWidthHandeled)
: used(0)
, dynamicSize(Dynamic::None)
, info()
info.specifier == Specifier::F)
= std::ios_base::fixed;
else if (info.specifier == Specifier::e
std::size_t size() const return used;
Dynamic isDynamicSize() const return dynamicSize;
// We pass the formatter to the stream first
// So we create a marker object used to print the actual argument.
// This will call apply() with the actual argument.
friend FormatterCheck operator<<(std::ostream& s, Formatter const& formatter)
return FormatterCheck(s, formatter);
private:
template<typename A>
void apply(std::ostream& s, A const& arg) const
if (dynamicSize == Dynamic::None)
using Actual = typename SignConversionOption<A>::Actual;
using Alternative = typename SignConversionOption<A>::Alternative;
if (std::type_index(typeid(Actual)) == std::type_index(*info.expectedType.first))
applyData(s, arg);
else if (std::type_index(typeid(Actual)) != std::type_index(typeid(Alternative)) && std::type_index(*info.expectedType.first) == std::type_index(typeid(Alternative)))
applyData(s, static_cast<Alternative const&>(arg));
else if (SignConversionOption<A>::allowIntConversion)
applyData(s, SignConversionOption<A>::convertToInt(arg));
else if (std::type_index(typeid(A)) == std::type_index(typeid(int)) && info.expectedType.second)
applyData(s, SignConversionOption<A>::truncate(arg, info.expectedType.second));
else
throw std::invalid_argument(std::string("Actual argument does not match supplied argument (or conversions): Expected(") + info.expectedType.first->name() + ") Got(" + typeid(A).name() + ")");
else
if (std::type_index(typeid(A)) != std::type_index(typeid(int)))
throw std::invalid_argument("Dynamic Width of Precision is not an int");
saveToStream(s, dynamicSize, arg);
template<typename A>
void applyData(std::ostream& s, A const& arg) const
// Only certain combinations of Specifier and Length are supported.
static AllowedType getType(Length length, Type type)
static std::map<std::pair<Type, Length>, AllowedType> typeMap =
#pragma vera-pushoff
Type::Int, Length::none, &typeid(int), 0,
Type::Int, Length::hh, &typeid(signed char), 0xFF,
Type::Int, Length::h, &typeid(short int), 0xFFFF,
Type::Int, Length::l, &typeid(long int), 0,
Type::Int, Length::ll, &typeid(long long int), 0,
Type::Int, Length::j, &typeid(std::intmax_t), 0,
Type::Int, Length::z, &typeid(std::size_t), 0,
Type::Int, Length::t, &typeid(std::ptrdiff_t), 0,
Type::UInt, Length::none, &typeid(unsigned int), 0,
Type::UInt, Length::hh, &typeid(unsigned char), 0xFF,
Type::UInt, Length::h, &typeid(unsigned short int), 0xFFFF,
Type::UInt, Length::l, &typeid(unsigned long int), 0,
Type::UInt, Length::ll, &typeid(unsigned long long int), 0,
Type::UInt, Length::j, &typeid(std::intmax_t), 0,
Type::UInt, Length::z, &typeid(std::size_t), 0,
Type::UInt, Length::t, &typeid(std::ptrdiff_t), 0,
Type::Float, Length::none, &typeid(double), 0, Type::Float, Length::l, &typeid(double), 0, Type::Float, Length::L, &typeid(long double), 0,
Type::Char, Length::none, &typeid(int), 0, Type::Char, Length::l, &typeid(std::wint_t), 0,
Type::String,Length::none, &typeid(char const*), 0, Type::String,Length::l, &typeid(wchar_t const*), 0,
Type::Pointer,Length::none,&typeid(void*), 0,
Type::Count, Length::none, &typeid(int*), 0,
Type::Count, Length::hh, &typeid(signed char*), 0,
Type::Count, Length::h, &typeid(short int*), 0,
Type::Count, Length::l, &typeid(long int*), 0,
Type::Count, Length::ll, &typeid(long long int*), 0,
Type::Count, Length::j, &typeid(std::intmax_t*), 0,
Type::Count, Length::z, &typeid(std::size_t*), 0,
Type::Count, Length::t, &typeid(std::ptrdiff_t*), 0
#pragma vera-pop
;
auto find = typeMap.find(type, length);
if (find == typeMap.end())
throw std::invalid_argument("Specifier and length are not a valid combination");
return find->second;
;
#endif
SignConversionOption
#ifndef THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
#define THORSANVIL_IOUTIL_SIGNCONVERSIONOPTION_H
namespace ThorsAnvil::IOUtil
/*
* When handling integer types some
* automatic conversions are allowed.
*
* This type handles these conversions.
* It is used by Formatter::apply()
*/
template<typename T>
struct SignConversionOption
using Actual = T; // The Current Type
using Alternative = T; // Acceptable alternative type we can cast from
static constexpr bool allowIntConversion = false; // Can we convert this type from int by call convertToInt()
static int convertToInt(T const&) return 0;
static int truncate(T const& arg, int mask) return 0;; // Int only we truncate the value by masking if top bits.
// The mask is retrieved from Formatter::getType()
;
template<>
struct SignConversionOption<char>
using Actual = char;
using Alternative = unsigned char;
static constexpr bool allowIntConversion = true;
static int convertToInt(char const& arg) return arg;
static int truncate(char const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<short>
using Actual = short;
using Alternative = unsigned short;
static constexpr bool allowIntConversion = true;
static int convertToInt(short const& arg) return arg;
static int truncate(short const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<int>
using Actual = int;
using Alternative = unsigned int;
static constexpr bool allowIntConversion = false;
static int convertToInt(int const&) return 0;
static int truncate(int const& arg, int mask) return arg & mask;;
;
template<>
struct SignConversionOption<long>
using Actual = long;
using Alternative = unsigned long;
static constexpr bool allowIntConversion = false;
static int convertToInt(long const&) return 0;
static int truncate(long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<long long>
using Actual = long long;
using Alternative = unsigned long long;
static constexpr bool allowIntConversion = false;
static int convertToInt(long long const&) return 0;
static int truncate(long long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned char>
using Actual = unsigned char;
using Alternative = char;
static constexpr bool allowIntConversion = true;
static int convertToInt(unsigned char const& arg) return arg;
static int truncate(unsigned char const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned short>
using Actual = unsigned short;
using Alternative = short;
static constexpr bool allowIntConversion = true;
static int convertToInt(unsigned short const& arg) return arg;
static int truncate(unsigned short const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned int>
using Actual = unsigned int;
using Alternative = int;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned int const&) return 0;
static int truncate(unsigned int const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned long>
using Actual = unsigned long;
using Alternative = long;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned long const&) return 0;
static int truncate(unsigned long const& arg, int mask) return 0;;
;
template<>
struct SignConversionOption<unsigned long long>
using Actual = unsigned long long;
using Alternative = long long;
static constexpr bool allowIntConversion = false;
static int convertToInt(unsigned long long const&) return 0;
static int truncate(unsigned long long const& arg, int mask) return 0;;
;
#endif
SaveToStream
#ifndef THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#define THORSANVIL_IOUTIL_SAVE_TO_STREAM_H
#include "FormatInfo.h"
#include <iostream>
namespace ThorsAnvil::IOUtil
template<typename T>
inline void saveToStream(std::ostream&, Dynamic, T const&)
template<>
inline void saveToStream(std::ostream& s, Dynamic pos, int const& size)
s.iword(static_cast<int>(pos)) = size;
#endif
c++ formatting serialization stream
edited Mar 4 at 19:51
Phrancis
14.6k645137
14.6k645137
asked Mar 4 at 19:20
Martin York
70.9k481244
70.9k481244
2
soo ... just for the record... what is stopping you from putting all four parts off this thing into one question? And why are you asking these questions in so quick succession?
â Vogel612â¦
Mar 4 at 19:37
2
@Vogel612: Because its big. Putting it all in one would be a huge code review. And we ask people to split up big code reviews. So quickly is because I wrote them all at the same time.
â Martin York
Mar 4 at 20:44
1
Does each part work on its own?
â Raystafarian
Mar 4 at 23:33
@Raystafarian: No. But if I wrote a browser, did you want me to post the whole browser in one code review or logical chunks? Also if you want to compile and test it you can download it from github and build in 4 steps: 1git clone git@github.com:Loki-Astari/ThorsIOUtil.git2cd ThorsIOUtil3./configure4make
â Martin York
Mar 5 at 16:55
No, I was just asking, I can't read that language.
â Raystafarian
Mar 5 at 21:21
 |Â
show 2 more comments
2
soo ... just for the record... what is stopping you from putting all four parts off this thing into one question? And why are you asking these questions in so quick succession?
â Vogel612â¦
Mar 4 at 19:37
2
@Vogel612: Because its big. Putting it all in one would be a huge code review. And we ask people to split up big code reviews. So quickly is because I wrote them all at the same time.
â Martin York
Mar 4 at 20:44
1
Does each part work on its own?
â Raystafarian
Mar 4 at 23:33
@Raystafarian: No. But if I wrote a browser, did you want me to post the whole browser in one code review or logical chunks? Also if you want to compile and test it you can download it from github and build in 4 steps: 1git clone git@github.com:Loki-Astari/ThorsIOUtil.git2cd ThorsIOUtil3./configure4make
â Martin York
Mar 5 at 16:55
No, I was just asking, I can't read that language.
â Raystafarian
Mar 5 at 21:21
2
2
soo ... just for the record... what is stopping you from putting all four parts off this thing into one question? And why are you asking these questions in so quick succession?
â Vogel612â¦
Mar 4 at 19:37
soo ... just for the record... what is stopping you from putting all four parts off this thing into one question? And why are you asking these questions in so quick succession?
â Vogel612â¦
Mar 4 at 19:37
2
2
@Vogel612: Because its big. Putting it all in one would be a huge code review. And we ask people to split up big code reviews. So quickly is because I wrote them all at the same time.
â Martin York
Mar 4 at 20:44
@Vogel612: Because its big. Putting it all in one would be a huge code review. And we ask people to split up big code reviews. So quickly is because I wrote them all at the same time.
â Martin York
Mar 4 at 20:44
1
1
Does each part work on its own?
â Raystafarian
Mar 4 at 23:33
Does each part work on its own?
â Raystafarian
Mar 4 at 23:33
@Raystafarian: No. But if I wrote a browser, did you want me to post the whole browser in one code review or logical chunks? Also if you want to compile and test it you can download it from github and build in 4 steps: 1
git clone git@github.com:Loki-Astari/ThorsIOUtil.git 2 cd ThorsIOUtil 3 ./configure 4 makeâ Martin York
Mar 5 at 16:55
@Raystafarian: No. But if I wrote a browser, did you want me to post the whole browser in one code review or logical chunks? Also if you want to compile and test it you can download it from github and build in 4 steps: 1
git clone git@github.com:Loki-Astari/ThorsIOUtil.git 2 cd ThorsIOUtil 3 ./configure 4 makeâ Martin York
Mar 5 at 16:55
No, I was just asking, I can't read that language.
â Raystafarian
Mar 5 at 21:21
No, I was just asking, I can't read that language.
â Raystafarian
Mar 5 at 21:21
 |Â
show 2 more comments
1 Answer
1
active
oldest
votes
up vote
2
down vote
The constexpr flag the code had was enough to build most of the templates. I renamed a few things for which I could find a good name, and left others untouched.
Reducing code duplication
SignConversionOption has extreme amount of code duplication. Templates can mostly take away the duplication:
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
One-lining the templates could make it even shorter, but I hope that formatting will lead intuition of whoever reads this.
Templates made tradeoff. By far not every C++ programmer can understand what is going on, because it uses multiple template techniques, which are not widespread.
Explanation
alternative_type
The logic gets a little bit nested. If type is not fundamental or not integral, just use defaults. If fundamental and integral, decide based on sign.
is_one_of
Pretty simple one. Try to match T against all of the Types, one by one.
convert_to_int
Basic tagged dispatch. Trying SFINAE will not work, tested. std::true_type is std::bool_constant<true>, and the same for std::false_type. "If you don't have any other ideas, use tagged dispatch." Somebody on SO told me this.
truncate
Piece of cake. Use overload and the fact that non-template always has higher priority in overload resolution.
Testing
Well, this is one is quite hard. I found one way to test it nicely, but it has one culprit that I don't like: use of typeid to provide useful message and demangle it in non-portable way.
Full code:
#include <type_traits>
#include <utility>
#include <stdexcept>
#include <typeinfo>
#include <string>
//from https://stackoverflow.com/a/4541470/4593721
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
std::string demangle(const char* name)
int status = -4; // some arbitrary value to eliminate the compiler warning
// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
;
return (status==0) ? res.get() : name ;
#else
// does nothing if not g++
std::string demangle(const char* name)
return name;
#endif
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
template <typename TestType, typename Alternative, bool should_convert>
void test_func(const TestType& value)
using behavior = sign_conversion_behavior<TestType>;
static_assert(!std::is_same_v<typename behavior::alternative_type, Alternative>, "Alternative type deduction doesn't work");
if constexpr (should_convert)
if (static_cast<int>(value) != behavior::convert_to_int(value))
throw std::logic_error(std::string"value conversion happens where it shouldn't " + demangle(typeid(TestType).name()));
int main()
test_func<int, unsigned int, false>(1);
test_func<short, unsigned short, true>(1);
test_func<unsigned int, int, false>(1);
test_func<unsigned short, short, true>(1);
Live on Wandbox.
Conclusion
The new code has very little code duplication. It added requirement for C++17 (soft requirement), and made code much more complex to comprehend. I'm not sure if it is a win, but certainy worth considering. It is constexpr ready though, which is quite big boon.
I'll comeback tomorrow or the day after. I hope I can do something with the interface.
â Incomputable
Mar 6 at 19:32
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
The constexpr flag the code had was enough to build most of the templates. I renamed a few things for which I could find a good name, and left others untouched.
Reducing code duplication
SignConversionOption has extreme amount of code duplication. Templates can mostly take away the duplication:
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
One-lining the templates could make it even shorter, but I hope that formatting will lead intuition of whoever reads this.
Templates made tradeoff. By far not every C++ programmer can understand what is going on, because it uses multiple template techniques, which are not widespread.
Explanation
alternative_type
The logic gets a little bit nested. If type is not fundamental or not integral, just use defaults. If fundamental and integral, decide based on sign.
is_one_of
Pretty simple one. Try to match T against all of the Types, one by one.
convert_to_int
Basic tagged dispatch. Trying SFINAE will not work, tested. std::true_type is std::bool_constant<true>, and the same for std::false_type. "If you don't have any other ideas, use tagged dispatch." Somebody on SO told me this.
truncate
Piece of cake. Use overload and the fact that non-template always has higher priority in overload resolution.
Testing
Well, this is one is quite hard. I found one way to test it nicely, but it has one culprit that I don't like: use of typeid to provide useful message and demangle it in non-portable way.
Full code:
#include <type_traits>
#include <utility>
#include <stdexcept>
#include <typeinfo>
#include <string>
//from https://stackoverflow.com/a/4541470/4593721
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
std::string demangle(const char* name)
int status = -4; // some arbitrary value to eliminate the compiler warning
// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
;
return (status==0) ? res.get() : name ;
#else
// does nothing if not g++
std::string demangle(const char* name)
return name;
#endif
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
template <typename TestType, typename Alternative, bool should_convert>
void test_func(const TestType& value)
using behavior = sign_conversion_behavior<TestType>;
static_assert(!std::is_same_v<typename behavior::alternative_type, Alternative>, "Alternative type deduction doesn't work");
if constexpr (should_convert)
if (static_cast<int>(value) != behavior::convert_to_int(value))
throw std::logic_error(std::string"value conversion happens where it shouldn't " + demangle(typeid(TestType).name()));
int main()
test_func<int, unsigned int, false>(1);
test_func<short, unsigned short, true>(1);
test_func<unsigned int, int, false>(1);
test_func<unsigned short, short, true>(1);
Live on Wandbox.
Conclusion
The new code has very little code duplication. It added requirement for C++17 (soft requirement), and made code much more complex to comprehend. I'm not sure if it is a win, but certainy worth considering. It is constexpr ready though, which is quite big boon.
I'll comeback tomorrow or the day after. I hope I can do something with the interface.
â Incomputable
Mar 6 at 19:32
add a comment |Â
up vote
2
down vote
The constexpr flag the code had was enough to build most of the templates. I renamed a few things for which I could find a good name, and left others untouched.
Reducing code duplication
SignConversionOption has extreme amount of code duplication. Templates can mostly take away the duplication:
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
One-lining the templates could make it even shorter, but I hope that formatting will lead intuition of whoever reads this.
Templates made tradeoff. By far not every C++ programmer can understand what is going on, because it uses multiple template techniques, which are not widespread.
Explanation
alternative_type
The logic gets a little bit nested. If type is not fundamental or not integral, just use defaults. If fundamental and integral, decide based on sign.
is_one_of
Pretty simple one. Try to match T against all of the Types, one by one.
convert_to_int
Basic tagged dispatch. Trying SFINAE will not work, tested. std::true_type is std::bool_constant<true>, and the same for std::false_type. "If you don't have any other ideas, use tagged dispatch." Somebody on SO told me this.
truncate
Piece of cake. Use overload and the fact that non-template always has higher priority in overload resolution.
Testing
Well, this is one is quite hard. I found one way to test it nicely, but it has one culprit that I don't like: use of typeid to provide useful message and demangle it in non-portable way.
Full code:
#include <type_traits>
#include <utility>
#include <stdexcept>
#include <typeinfo>
#include <string>
//from https://stackoverflow.com/a/4541470/4593721
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
std::string demangle(const char* name)
int status = -4; // some arbitrary value to eliminate the compiler warning
// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
;
return (status==0) ? res.get() : name ;
#else
// does nothing if not g++
std::string demangle(const char* name)
return name;
#endif
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
template <typename TestType, typename Alternative, bool should_convert>
void test_func(const TestType& value)
using behavior = sign_conversion_behavior<TestType>;
static_assert(!std::is_same_v<typename behavior::alternative_type, Alternative>, "Alternative type deduction doesn't work");
if constexpr (should_convert)
if (static_cast<int>(value) != behavior::convert_to_int(value))
throw std::logic_error(std::string"value conversion happens where it shouldn't " + demangle(typeid(TestType).name()));
int main()
test_func<int, unsigned int, false>(1);
test_func<short, unsigned short, true>(1);
test_func<unsigned int, int, false>(1);
test_func<unsigned short, short, true>(1);
Live on Wandbox.
Conclusion
The new code has very little code duplication. It added requirement for C++17 (soft requirement), and made code much more complex to comprehend. I'm not sure if it is a win, but certainy worth considering. It is constexpr ready though, which is quite big boon.
I'll comeback tomorrow or the day after. I hope I can do something with the interface.
â Incomputable
Mar 6 at 19:32
add a comment |Â
up vote
2
down vote
up vote
2
down vote
The constexpr flag the code had was enough to build most of the templates. I renamed a few things for which I could find a good name, and left others untouched.
Reducing code duplication
SignConversionOption has extreme amount of code duplication. Templates can mostly take away the duplication:
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
One-lining the templates could make it even shorter, but I hope that formatting will lead intuition of whoever reads this.
Templates made tradeoff. By far not every C++ programmer can understand what is going on, because it uses multiple template techniques, which are not widespread.
Explanation
alternative_type
The logic gets a little bit nested. If type is not fundamental or not integral, just use defaults. If fundamental and integral, decide based on sign.
is_one_of
Pretty simple one. Try to match T against all of the Types, one by one.
convert_to_int
Basic tagged dispatch. Trying SFINAE will not work, tested. std::true_type is std::bool_constant<true>, and the same for std::false_type. "If you don't have any other ideas, use tagged dispatch." Somebody on SO told me this.
truncate
Piece of cake. Use overload and the fact that non-template always has higher priority in overload resolution.
Testing
Well, this is one is quite hard. I found one way to test it nicely, but it has one culprit that I don't like: use of typeid to provide useful message and demangle it in non-portable way.
Full code:
#include <type_traits>
#include <utility>
#include <stdexcept>
#include <typeinfo>
#include <string>
//from https://stackoverflow.com/a/4541470/4593721
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
std::string demangle(const char* name)
int status = -4; // some arbitrary value to eliminate the compiler warning
// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
;
return (status==0) ? res.get() : name ;
#else
// does nothing if not g++
std::string demangle(const char* name)
return name;
#endif
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
template <typename TestType, typename Alternative, bool should_convert>
void test_func(const TestType& value)
using behavior = sign_conversion_behavior<TestType>;
static_assert(!std::is_same_v<typename behavior::alternative_type, Alternative>, "Alternative type deduction doesn't work");
if constexpr (should_convert)
if (static_cast<int>(value) != behavior::convert_to_int(value))
throw std::logic_error(std::string"value conversion happens where it shouldn't " + demangle(typeid(TestType).name()));
int main()
test_func<int, unsigned int, false>(1);
test_func<short, unsigned short, true>(1);
test_func<unsigned int, int, false>(1);
test_func<unsigned short, short, true>(1);
Live on Wandbox.
Conclusion
The new code has very little code duplication. It added requirement for C++17 (soft requirement), and made code much more complex to comprehend. I'm not sure if it is a win, but certainy worth considering. It is constexpr ready though, which is quite big boon.
The constexpr flag the code had was enough to build most of the templates. I renamed a few things for which I could find a good name, and left others untouched.
Reducing code duplication
SignConversionOption has extreme amount of code duplication. Templates can mostly take away the duplication:
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
One-lining the templates could make it even shorter, but I hope that formatting will lead intuition of whoever reads this.
Templates made tradeoff. By far not every C++ programmer can understand what is going on, because it uses multiple template techniques, which are not widespread.
Explanation
alternative_type
The logic gets a little bit nested. If type is not fundamental or not integral, just use defaults. If fundamental and integral, decide based on sign.
is_one_of
Pretty simple one. Try to match T against all of the Types, one by one.
convert_to_int
Basic tagged dispatch. Trying SFINAE will not work, tested. std::true_type is std::bool_constant<true>, and the same for std::false_type. "If you don't have any other ideas, use tagged dispatch." Somebody on SO told me this.
truncate
Piece of cake. Use overload and the fact that non-template always has higher priority in overload resolution.
Testing
Well, this is one is quite hard. I found one way to test it nicely, but it has one culprit that I don't like: use of typeid to provide useful message and demangle it in non-portable way.
Full code:
#include <type_traits>
#include <utility>
#include <stdexcept>
#include <typeinfo>
#include <string>
//from https://stackoverflow.com/a/4541470/4593721
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
std::string demangle(const char* name)
int status = -4; // some arbitrary value to eliminate the compiler warning
// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
;
return (status==0) ? res.get() : name ;
#else
// does nothing if not g++
std::string demangle(const char* name)
return name;
#endif
namespace details
...);
template <typename T>
struct sign_conversion_behavior
static constexpr bool is_eligible = std::is_fundamental_v<T> &&
std::is_integral_v<T>;
static constexpr bool is_convertible = details::is_one_of<T,
char, short,
unsigned char, unsigned short>;
using type = T;
using alternative_type = details::alternative_type<T, is_eligible>;
static constexpr int convert_to_int(const T& val)
return do_convert(std::bool_constant<is_convertible>, val);
static constexpr int truncate(const int& arg, int mask) return arg & mask;
template <typename U>
static constexpr int truncate(const U&, int) return 0;
private:
static constexpr int do_convert(std::true_type, const T& val) return val;
static constexpr int do_convert(std::false_type, const T&) return 0;
;
template <typename TestType, typename Alternative, bool should_convert>
void test_func(const TestType& value)
using behavior = sign_conversion_behavior<TestType>;
static_assert(!std::is_same_v<typename behavior::alternative_type, Alternative>, "Alternative type deduction doesn't work");
if constexpr (should_convert)
if (static_cast<int>(value) != behavior::convert_to_int(value))
throw std::logic_error(std::string"value conversion happens where it shouldn't " + demangle(typeid(TestType).name()));
int main()
test_func<int, unsigned int, false>(1);
test_func<short, unsigned short, true>(1);
test_func<unsigned int, int, false>(1);
test_func<unsigned short, short, true>(1);
Live on Wandbox.
Conclusion
The new code has very little code duplication. It added requirement for C++17 (soft requirement), and made code much more complex to comprehend. I'm not sure if it is a win, but certainy worth considering. It is constexpr ready though, which is quite big boon.
answered Mar 6 at 19:29
Incomputable
6,11721246
6,11721246
I'll comeback tomorrow or the day after. I hope I can do something with the interface.
â Incomputable
Mar 6 at 19:32
add a comment |Â
I'll comeback tomorrow or the day after. I hope I can do something with the interface.
â Incomputable
Mar 6 at 19:32
I'll comeback tomorrow or the day after. I hope I can do something with the interface.
â Incomputable
Mar 6 at 19:32
I'll comeback tomorrow or the day after. I hope I can do something with the interface.
â Incomputable
Mar 6 at 19:32
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188811%2fc-string-formatter-again-part-3%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
2
soo ... just for the record... what is stopping you from putting all four parts off this thing into one question? And why are you asking these questions in so quick succession?
â Vogel612â¦
Mar 4 at 19:37
2
@Vogel612: Because its big. Putting it all in one would be a huge code review. And we ask people to split up big code reviews. So quickly is because I wrote them all at the same time.
â Martin York
Mar 4 at 20:44
1
Does each part work on its own?
â Raystafarian
Mar 4 at 23:33
@Raystafarian: No. But if I wrote a browser, did you want me to post the whole browser in one code review or logical chunks? Also if you want to compile and test it you can download it from github and build in 4 steps: 1
git clone git@github.com:Loki-Astari/ThorsIOUtil.git2cd ThorsIOUtil3./configure4makeâ Martin York
Mar 5 at 16:55
No, I was just asking, I can't read that language.
â Raystafarian
Mar 5 at 21:21