As with all those C++ base types, unfortunately they are giving you only rough size guarantees. For Lumiera, I defined short hand names some time ago, notably because the explicit long X and short X qualifiers do not work with ''constructor syntax'' for direct conversions. But I choose the name `f128` at that time -- which might raise ill-guided expectations, since the `long double` is not that long on relevant linux 64bit platforms; it can just hold 64bit of mantissa at least however. As replacement, I'll use `lflo` now, because it has also 4 letters, looks somewhat weird (and thus catches attention) and points into the right direction. I considered `lfloat` as alternative, but that looks too much like `float`, visually...
330 lines
10 KiB
C++
330 lines
10 KiB
C++
/*
|
||
FORMAT-OBJ.hpp - simple means to display an object
|
||
|
||
Copyright (C)
|
||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||
under the terms of the GNU General Public License as published by the
|
||
Free Software Foundation; either version 2 of the License, or (at your
|
||
option) any later version. See the file COPYING for further details.
|
||
|
||
*/
|
||
|
||
|
||
/** @file format-obj.hpp
|
||
** Simple functions to represent objects, for debugging and diagnostics.
|
||
** The helpers provided here are rather commonplace, but written in a way
|
||
** to incur only modest header inclusion load. It should be OK to use them
|
||
** even on interface headers in widespread use.
|
||
** - util::toString() performs a failsafe to-String conversion, thereby preferring a
|
||
** built-in conversion operator, falling back to a lexical conversion (boost)
|
||
** or just an unmangled and simplified type string as default.
|
||
** - util::typedString() combines this with an always visible type display
|
||
** - lib::meta::demangleCxx() uses the built-in compiler support to translate a mangled
|
||
** type-ID (as given by `typeid(TY).name()`) into a readable, fully qualified
|
||
** C++ type name. This is only supported for GNU compatible compilers.
|
||
**
|
||
** @todo is it possible to stash away the `boost::lexical_cast` behind a custom facade,
|
||
** the way we did it for `boost::format`? This would reduce inclusion cost...
|
||
**
|
||
** @see FormatHelper_test
|
||
** @see [frontend for boost::format, printf-style](\ref format-string.hpp)
|
||
**
|
||
*/
|
||
|
||
|
||
#ifndef LIB_FORMAT_OBJ_H
|
||
#define LIB_FORMAT_OBJ_H
|
||
|
||
#include "lib/symbol.hpp"
|
||
#include "lib/meta/trait.hpp"
|
||
|
||
#include <boost/lexical_cast.hpp>
|
||
|
||
|
||
namespace std { // forward declaration to avoid including <iostream>
|
||
|
||
template<typename C>
|
||
struct char_traits;
|
||
|
||
template<typename C, class _TRAITS>
|
||
class basic_ostream;
|
||
|
||
using ostream = basic_ostream<char, char_traits<char>>;
|
||
}
|
||
|
||
|
||
namespace lib {
|
||
class Literal;
|
||
|
||
namespace meta {
|
||
|
||
std::string demangleCxx (lib::Literal rawName);
|
||
std::string humanReadableTypeID (lib::Literal);
|
||
std::string primaryTypeComponent (lib::Literal);
|
||
std::string sanitisedFullTypeName(lib::Literal);
|
||
|
||
}}// namespace lib::meta
|
||
|
||
|
||
|
||
|
||
|
||
|
||
namespace util {
|
||
|
||
std::string showDouble (double) noexcept;
|
||
std::string showFloat (float) noexcept;
|
||
std::string showSize (size_t) noexcept;
|
||
std::string showAdr (void const* addr) noexcept;
|
||
|
||
/** preconfigured format for pretty-printing of addresses */
|
||
std::ostream& showAdr (std::ostream&, void const* addr);
|
||
|
||
/** renders the size_t in hex, optionally only trailing bytes */
|
||
std::string showHash (size_t hash, uint showBytes=8) noexcept;
|
||
|
||
inline std::string
|
||
showHashLSB (size_t hash) noexcept
|
||
{
|
||
return showHash(hash,1);
|
||
}
|
||
|
||
std::string showDecimal (double) noexcept;
|
||
std::string showDecimal (float) noexcept;
|
||
std::string showDecimal (lflo) noexcept;
|
||
std::string showComplete (double)noexcept;
|
||
std::string showComplete (float) noexcept;
|
||
std::string showComplete (lflo) noexcept;
|
||
|
||
|
||
|
||
|
||
namespace { // helper for printing type diagnostics
|
||
|
||
template<typename X>
|
||
struct TypeDiagnostics
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "";
|
||
static constexpr auto postfix = "";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<const X>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "const ";
|
||
static constexpr auto postfix = "";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<X&>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "";
|
||
static constexpr auto postfix = "&";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<X&&>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "";
|
||
static constexpr auto postfix = " &&";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<X const&>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "";
|
||
static constexpr auto postfix = " const&";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<X const&&>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "const ";
|
||
static constexpr auto postfix = " &&";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<X *>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "";
|
||
static constexpr auto postfix = " *";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<const X *>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "const ";
|
||
static constexpr auto postfix = " *";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<const X * const>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "const ";
|
||
static constexpr auto postfix = " * const";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<X * const>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "";
|
||
static constexpr auto postfix = " * const";
|
||
};
|
||
template<typename X>
|
||
struct TypeDiagnostics<X * const *>
|
||
{
|
||
using Type = X;
|
||
static constexpr auto prefix = "";
|
||
static constexpr auto postfix = " * const *";
|
||
};
|
||
}//(End)decoration helper
|
||
|
||
/** diagnostic type output, including const and similar adornments
|
||
* @warning operates after-the-fact and relies on mangled type names
|
||
* plus several heuristics. Output might thus not be entirely correct,
|
||
* especially when several levels of const, pointer and references are involved.
|
||
* If in doubt, place the test::TypeDebugger<T> to reveal the type as the compiler sees it.
|
||
* @remarks the function lib::meta::typeStr was deliberately written such as to remove type adornments
|
||
* and does not work on all kinds of reference. This helper complements this and
|
||
* attempts to preserve const-ness, pointer and reference decoration.
|
||
*/
|
||
template<typename X>
|
||
inline std::string
|
||
showType()
|
||
{
|
||
using Case = TypeDiagnostics<X>;
|
||
using Type = Case::Type;
|
||
|
||
return Case::prefix
|
||
+ lib::meta::humanReadableTypeID (typeid(Type).name())
|
||
+ Case::postfix;
|
||
}
|
||
|
||
template<typename...TS>
|
||
inline std::string
|
||
showTypes()
|
||
{
|
||
return "<| " + ((showType<TS>()+", ") + ... + "|>");
|
||
}
|
||
|
||
|
||
|
||
|
||
namespace {
|
||
/** toggle to prefer specialisation with direct lexical conversion */
|
||
template<typename X>
|
||
using enable_LexicalConversion = lib::meta::enable_if< lib::meta::use_LexicalConversion<X>>;
|
||
|
||
template<typename SP>
|
||
using show_SmartPointer = lib::meta::enable_if< lib::meta::is_smart_ptr<typename lib::meta::Strip<SP>::TypeReferred>>;
|
||
}
|
||
|
||
|
||
|
||
/* === generalise the failsafe string conversion === */
|
||
|
||
/** @note base case is defined in meta/util.hpp */
|
||
template<typename X>
|
||
struct StringConv<X, enable_LexicalConversion<X>>
|
||
{
|
||
static std::string
|
||
invoke (X const& val) noexcept
|
||
try { return boost::lexical_cast<std::string> (val); }
|
||
catch(...) { return FAILURE_INDICATOR; }
|
||
};
|
||
|
||
template<typename SP>
|
||
struct StringConv<SP, show_SmartPointer<SP>>
|
||
{
|
||
static std::string
|
||
invoke (SP const& smP) noexcept
|
||
try { return showSmartPtr (smP, lib::meta::typeSymbol(smP)); }
|
||
catch(...) { return FAILURE_INDICATOR; }
|
||
};
|
||
|
||
/** explicit specialisation to control precision of double values.
|
||
* @note we set an explicit precision, since this is a diagnostic facility
|
||
* and we typically do not want to see all digits, but, for test code,
|
||
* we do want a predictable string representation of simple fractional
|
||
* values like `0.1` (which can not be represented as binary floats)
|
||
*/
|
||
template<>
|
||
struct StringConv<double>
|
||
{
|
||
static std::string
|
||
invoke (double val) noexcept
|
||
{
|
||
return util::showDouble (val);
|
||
}
|
||
};
|
||
template<>
|
||
struct StringConv<float>
|
||
{
|
||
static std::string
|
||
invoke (float val) noexcept
|
||
{
|
||
return util::showFloat (val);
|
||
}
|
||
};
|
||
template<>
|
||
struct StringConv<bool>
|
||
{
|
||
static std::string
|
||
invoke (bool val) noexcept
|
||
{
|
||
return util::showBool (val);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
/**
|
||
* get some string representation of any object, reliably.
|
||
* A custom string conversion operator is invoked, if applicable,
|
||
* while all lexically convertible types (numbers etc) are treated
|
||
* by boost::lexical_cast. For double or float values, hard wired
|
||
* rounding to a fixed number of digits will be performed, to yield
|
||
* a predictable display of printed unit-test results.
|
||
* @remark while the actual parameter is passed by const-ref,
|
||
* cv-qualifiactions and references are stripped from the type
|
||
* @note Deliberately there is no magic detection/support for pointers.
|
||
* This function _must not be overloaded_ (to avoid ambiguities
|
||
* in more elaborate template instantiations).
|
||
* @remark Since 10/2023 an attempt was made to solve this problem down
|
||
* in `StringConf<TY>`, yet this solution might be brittle. If you
|
||
* want pointers to be indicated (with address), consider using
|
||
* util::showPtr explicitly.
|
||
*/
|
||
template<typename TY>
|
||
inline std::string
|
||
toString (TY const& val) noexcept
|
||
{
|
||
using PlainVal = lib::meta::Strip<TY>::TypeReferred;
|
||
return StringConv<PlainVal>::invoke (val);
|
||
}
|
||
|
||
|
||
/**
|
||
* indicate type and possibly a (custom) conversion to string
|
||
* @return human readable type name '|' string representation.
|
||
* or just the type, when no string representation available
|
||
*/
|
||
template<typename TY>
|
||
inline std::string
|
||
typedString (TY const& val) noexcept
|
||
try {
|
||
std::string repr = StringConv<TY>::invoke (val);
|
||
return 0 == repr.rfind("«", 0)? repr
|
||
: "«"+typeStr(val)+"»|"+repr;
|
||
}
|
||
catch(...)
|
||
{ return FAILURE_INDICATOR; }
|
||
|
||
|
||
|
||
} // namespace util
|
||
#endif /*LIB_FORMAT_OBJ_H*/
|