lumiera_documentation/src/lib/format-obj.hpp
Ichthyostega 0760525ea5 Library: deep dive into type reconciliation and tuple handling
This commit is the aggregated result of extended research
that is only loosely related to current development topics.

One theme was the construction of nested data structures using tuples,
which faces some difficulties due to tuple manipulations being limited
almost entirely to compile-time processing. However, the barrier between
compile-time and runtime can be overcome with the help of a dispatcher
table -- which leads to the secondary problem to handle the diverse
tuple elements with a common, generic functor. Such a functor can be
constructed, if and only if those element types are ''reconcilable''

The STDLIB provides two metafunctions, `std::common_type` and
`std::common_refernce`, which however pose some challenges for the
task at hand, since they include additional processing. In the end,
a custom version of the ''basic type reconciliation'' was implemented
and tested, and then used as foundation to build a dispatcher table
for runtime-indexed access and iteration over tuples.

As an aside, several deeply hidden bugs in our library code were resolved,
and the tuple-helpers, tuple string rendering and associated utilities
were regrouped and modernised.

As result of this research work, the following components are provided:
- meta/common-result
- meta/fun-trampoline
- meta/tuple-index-adaptor
2026-04-07 06:32:23 +02:00

330 lines
10 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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 (f128) noexcept;
std::string showComplete (double)noexcept;
std::string showComplete (float) noexcept;
std::string showComplete (f128) 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*/