2008-07-30 03:56:13 +02:00
|
|
|
/*
|
|
|
|
|
UTIL.hpp - metaprogramming helpers and utilities
|
2010-12-17 23:28:49 +01:00
|
|
|
|
2008-07-30 03:56:13 +02:00
|
|
|
Copyright (C) Lumiera.org
|
|
|
|
|
2008, Hermann Vosseler <Ichthyostega@web.de>
|
2010-12-17 23:28:49 +01:00
|
|
|
|
2008-07-30 03:56:13 +02:00
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
|
modify it under the terms of the GNU General Public License as
|
2010-12-17 23:28:49 +01:00
|
|
|
published by the Free Software Foundation; either version 2 of
|
|
|
|
|
the License, or (at your option) any later version.
|
|
|
|
|
|
2008-07-30 03:56:13 +02:00
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
2010-12-17 23:28:49 +01:00
|
|
|
|
2008-07-30 03:56:13 +02:00
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
2010-12-17 23:28:49 +01:00
|
|
|
|
2008-07-30 03:56:13 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
2011-12-30 03:45:10 +01:00
|
|
|
/** @file util.hpp
|
|
|
|
|
** Simple and lightweight helpers for metaprogramming and type detection.
|
|
|
|
|
** This header is a collection of very basic type detection and metaprogramming utilities.
|
|
|
|
|
** @warning indirectly, this header gets included into the majority of compilation units.
|
|
|
|
|
** Avoid anything here which increases compilation times or adds much debugging info.
|
|
|
|
|
**
|
|
|
|
|
** @see MetaUtils_test
|
|
|
|
|
** @see trait.hpp
|
|
|
|
|
** @see typelist.hpp
|
|
|
|
|
**
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
2011-12-03 02:56:50 +01:00
|
|
|
#ifndef LIB_META_UTIL_H
|
|
|
|
|
#define LIB_META_UTIL_H
|
2008-07-30 03:56:13 +02:00
|
|
|
|
2012-01-07 03:11:51 +01:00
|
|
|
|
2016-01-05 20:10:20 +01:00
|
|
|
#include <typeinfo>
|
|
|
|
|
|
|
|
|
|
namespace std { // forward declaration for std::string...
|
|
|
|
|
|
|
|
|
|
template<typename C>
|
|
|
|
|
struct char_traits;
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
class allocator;
|
|
|
|
|
|
|
|
|
|
template<typename c, class TRAIT, class _ALLO>
|
|
|
|
|
class basic_string;
|
|
|
|
|
|
|
|
|
|
using string = basic_string<char, char_traits<char>, allocator<char>>;
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-07 03:11:51 +01:00
|
|
|
|
|
|
|
|
|
2011-12-03 02:56:50 +01:00
|
|
|
namespace lib {
|
2016-01-05 23:34:53 +01:00
|
|
|
class Literal;
|
|
|
|
|
class Symbol;
|
|
|
|
|
|
2011-12-03 02:56:50 +01:00
|
|
|
namespace meta {
|
2012-01-07 03:11:51 +01:00
|
|
|
|
2016-01-05 20:53:31 +01:00
|
|
|
/* === conditional definition selector === */
|
|
|
|
|
|
|
|
|
|
template <bool B, class T = void>
|
|
|
|
|
struct enable_if_c {
|
|
|
|
|
typedef T type;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
|
struct enable_if_c<false, T> {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** SFINAE helper to control the visibility of specialisations and overloads.
|
|
|
|
|
* \par explanation
|
|
|
|
|
* This template needs to be interspersed somehow into a type expression, which
|
|
|
|
|
* is driven by an external, primary type parameter. Thus, it is possible to use
|
|
|
|
|
* it on an extraneous, possibly default template parameter, or when forming the
|
|
|
|
|
* return type of a function. The purpose is to remove a given definition from
|
|
|
|
|
* sight, unless a boolean condition `Cond::value` holds true. In the typical
|
|
|
|
|
* usage, this condition is suppled by a _metafunction_, i.e. a template, which
|
|
|
|
|
* detects some feature or other circumstantial condition with the types involved.
|
|
|
|
|
* @remarks this is a widely used facility, available both from boost and from
|
|
|
|
|
* the standard library. For the most common case, we roll our own
|
|
|
|
|
* variant here, which is slightly stripped down and a tiny bit
|
|
|
|
|
* more concise than the boost variant. This way, we can avoid
|
|
|
|
|
* a lot of boost inclusions, which always bear some weight.
|
|
|
|
|
* @see [std::enable_if](http://en.cppreference.com/w/cpp/types/enable_if)
|
|
|
|
|
*/
|
|
|
|
|
template <class Cond, class T = void>
|
|
|
|
|
using enable_if = typename enable_if_c<Cond::value, T>::type;
|
|
|
|
|
|
|
|
|
|
template <class Cond, class T = void>
|
|
|
|
|
using disable_if = typename enable_if_c<not Cond::value, T>::type;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* === building metafunctions === */
|
2012-01-07 03:11:51 +01:00
|
|
|
|
2016-01-05 20:10:20 +01:00
|
|
|
/** helper types to detect the overload resolution chosen by the compiler */
|
2012-01-07 03:11:51 +01:00
|
|
|
|
2008-08-03 16:47:38 +02:00
|
|
|
typedef char Yes_t;
|
2011-12-30 03:45:10 +01:00
|
|
|
struct No_t { char more_than_one[4]; };
|
2012-01-07 03:11:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2008-07-30 03:56:13 +02:00
|
|
|
|
2011-12-30 03:45:10 +01:00
|
|
|
/** detect possibility of a conversion to string.
|
2013-09-01 17:36:05 +02:00
|
|
|
* Naive implementation just trying the direct conversion.
|
2011-12-30 03:45:10 +01:00
|
|
|
* The embedded constant #value will be true in case this succeeds.
|
|
|
|
|
* Might fail in more tricky situations (references, const, volatile)
|
2016-01-05 20:10:20 +01:00
|
|
|
* @see \ref format-obj.hpp more elaborate solution including lexical_cast
|
2011-12-30 03:45:10 +01:00
|
|
|
*/
|
2016-01-08 01:00:05 +01:00
|
|
|
template<typename X>
|
2011-12-30 05:13:27 +01:00
|
|
|
struct can_convertToString
|
2011-12-30 03:45:10 +01:00
|
|
|
{
|
2016-01-08 01:00:05 +01:00
|
|
|
static X & probe();
|
2011-12-30 03:45:10 +01:00
|
|
|
|
|
|
|
|
static Yes_t check(std::string);
|
|
|
|
|
static No_t check(...);
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
static const bool value = (sizeof(Yes_t)==sizeof(check(probe())));
|
|
|
|
|
};
|
|
|
|
|
|
2016-01-08 01:00:05 +01:00
|
|
|
/** toggle for explicit specialisations */
|
|
|
|
|
template<typename X>
|
|
|
|
|
using enable_CustomStringConversion = enable_if<can_convertToString<X>>;
|
|
|
|
|
|
|
|
|
|
|
2011-12-30 03:45:10 +01:00
|
|
|
|
2011-12-31 01:30:07 +01:00
|
|
|
/** strip const from type: naive implementation */
|
|
|
|
|
template<typename T>
|
|
|
|
|
struct UnConst
|
|
|
|
|
{
|
|
|
|
|
typedef T Type;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
struct UnConst<const T>
|
|
|
|
|
{
|
|
|
|
|
typedef T Type;
|
|
|
|
|
};
|
|
|
|
|
template<typename T>
|
|
|
|
|
struct UnConst<const T *>
|
|
|
|
|
{
|
|
|
|
|
typedef T* Type;
|
|
|
|
|
};
|
|
|
|
|
template<typename T>
|
|
|
|
|
struct UnConst<T * const>
|
|
|
|
|
{
|
|
|
|
|
typedef T* Type;
|
|
|
|
|
};
|
|
|
|
|
template<typename T>
|
|
|
|
|
struct UnConst<const T * const>
|
|
|
|
|
{
|
|
|
|
|
typedef T* Type;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-12-03 02:56:50 +01:00
|
|
|
/** Trait template for detecting a typelist type.
|
|
|
|
|
* For example, this allows to write specialisations with the help of
|
|
|
|
|
* boost::enable_if
|
|
|
|
|
*/
|
|
|
|
|
template<typename TY>
|
|
|
|
|
class is_Typelist
|
|
|
|
|
{
|
|
|
|
|
template<class X>
|
|
|
|
|
static Yes_t check(typename X::List *);
|
|
|
|
|
template<class>
|
|
|
|
|
static No_t check(...);
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
static const bool value = (sizeof(Yes_t)==sizeof(check<TY>(0)));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-01-05 23:34:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ==== generic string representation ==== */
|
|
|
|
|
|
|
|
|
|
/** pretty-print an internal C++ type representation
|
|
|
|
|
* @see \ref format-obj.cpp implementation
|
|
|
|
|
*/
|
|
|
|
|
std::string humanReadableTypeID (lib::Literal);
|
|
|
|
|
|
2016-01-08 08:20:59 +01:00
|
|
|
/** extract core name component from a raw type spec
|
|
|
|
|
* @return simple identifier possibly "the" type
|
|
|
|
|
* @warning implemented lexically, not necessarily correct!
|
|
|
|
|
*/
|
|
|
|
|
std::string primaryTypeComponent (lib::Literal);
|
|
|
|
|
|
|
|
|
|
/** build a sanitised ID from full type name */
|
|
|
|
|
std::string sanitisedFullTypeName(lib::Literal);
|
|
|
|
|
|
|
|
|
|
/** reverse the effect of C++ name mangling.
|
|
|
|
|
* @return string in language-level form of a C++ type or object name,
|
|
|
|
|
* or a string with the original input if demangling fails.
|
|
|
|
|
* @warning implementation relies on the cross vendor C++ ABI in use
|
|
|
|
|
* by GCC and compatible compilers, so portability is limited.
|
|
|
|
|
* The implementation is accessed through libStdC++
|
|
|
|
|
* Name representation in emitted object code and type IDs is
|
|
|
|
|
* essentially an implementation detail and subject to change.
|
|
|
|
|
*/
|
2016-01-05 23:34:53 +01:00
|
|
|
std::string demangleCxx (lib::Literal rawName);
|
|
|
|
|
|
|
|
|
|
|
2016-01-08 08:20:59 +01:00
|
|
|
extern const std::string FAILURE_INDICATOR;
|
|
|
|
|
extern const std::string VOID_INDICATOR;
|
|
|
|
|
|
|
|
|
|
|
2016-01-05 23:34:53 +01:00
|
|
|
|
|
|
|
|
/** failsafe human readable type display
|
|
|
|
|
* @return string representing the C++ type.
|
|
|
|
|
* @remarks the purpose of this function is diagnostics
|
|
|
|
|
* and unit-testing. When possible, RTTI is exposed, otherwise
|
|
|
|
|
* the implementation falls back on the static type as seen by
|
|
|
|
|
* the compiler on usage site. An attempt is made to de-mangle
|
|
|
|
|
* and further simplify the type string, leaving out some common
|
|
|
|
|
* (hard wired) namespace prefixes, and stripping typical adornments
|
|
|
|
|
* like `const`, `*` and `&`
|
sanity: how to pass 'anything' properly to the type diagnostics function
turns out this is a tricky situation.
We want to accept pretty mutch everything, yet we want to get a grip
on anything object-like, so to reveal available RTTI information.
Now, given the way C++ template substitution works, the 'TY const&' overload
wins with only a few exceptions. The reason is, C++ invokes most functions
passing the concrete argument as reference, unless this is not possible,
because the concrete artument is a rvalue. The automatic reduction of
reference expressions does the rest. Consequently the overload with 'const&'
turns out to be the best match even when we invoke the function with a
pointer expression, which would then be made into a pointer-to-a pointer
by our forward call.
There are two remedies for this dilemma:
- make the second overload just typeStr (TY&)
- explicitly remove the second overload for pointers
The first solution unfortunately would rule out passing of anonymous
objects like concatenated strings; in fact it would rule out passing
rvalues as such. While the second solution, chosen here, works really
for everything, and also has the nice side effect of stripping away
any const, pointer and reference adornements elegantly before we
even start to analyse the type.
The only downside of this solution is that it looks intimidating
to the casual reader. Well, I'd say, get used to it.
2016-01-09 01:32:02 +01:00
|
|
|
* @remarks almost all calls will enter through the `const&` variant of this
|
|
|
|
|
* function, since C++ considers this best match in template substitution.
|
|
|
|
|
* Thus, we deliberately force calls with pointer to enter here, since we
|
|
|
|
|
* do want the pointer itself (and not a pointer to the pointer). We then
|
|
|
|
|
* pass the "object" as so called "glvalue" to the `typeid()` function,
|
|
|
|
|
* so to get the evaluation of RTTI, when applicable.
|
2016-01-05 23:34:53 +01:00
|
|
|
* @warning this function does string transformations behind the scenes,
|
|
|
|
|
* and thus should not be used in performance critical context. Moreover,
|
|
|
|
|
* the returned type string is not necessarily exact and re-parsable.
|
|
|
|
|
*/
|
|
|
|
|
template<typename TY>
|
|
|
|
|
inline std::string
|
2016-01-07 20:17:07 +01:00
|
|
|
typeStr (TY const* obj =nullptr) noexcept
|
2016-01-06 04:04:56 +01:00
|
|
|
try {
|
2016-01-08 08:20:59 +01:00
|
|
|
auto mangledType = obj? typeid(*obj).name()
|
2016-01-06 04:04:56 +01:00
|
|
|
: typeid(TY).name();
|
|
|
|
|
return humanReadableTypeID (mangledType);
|
|
|
|
|
}
|
|
|
|
|
catch(...)
|
2016-01-08 08:20:59 +01:00
|
|
|
{ return FAILURE_INDICATOR; }
|
2016-01-05 23:34:53 +01:00
|
|
|
|
|
|
|
|
template<typename TY>
|
sanity: how to pass 'anything' properly to the type diagnostics function
turns out this is a tricky situation.
We want to accept pretty mutch everything, yet we want to get a grip
on anything object-like, so to reveal available RTTI information.
Now, given the way C++ template substitution works, the 'TY const&' overload
wins with only a few exceptions. The reason is, C++ invokes most functions
passing the concrete argument as reference, unless this is not possible,
because the concrete artument is a rvalue. The automatic reduction of
reference expressions does the rest. Consequently the overload with 'const&'
turns out to be the best match even when we invoke the function with a
pointer expression, which would then be made into a pointer-to-a pointer
by our forward call.
There are two remedies for this dilemma:
- make the second overload just typeStr (TY&)
- explicitly remove the second overload for pointers
The first solution unfortunately would rule out passing of anonymous
objects like concatenated strings; in fact it would rule out passing
rvalues as such. While the second solution, chosen here, works really
for everything, and also has the nice side effect of stripping away
any const, pointer and reference adornements elegantly before we
even start to analyse the type.
The only downside of this solution is that it looks intimidating
to the casual reader. Well, I'd say, get used to it.
2016-01-09 01:32:02 +01:00
|
|
|
inline disable_if<std::is_pointer<TY>,
|
|
|
|
|
std::string >
|
2016-01-06 04:04:56 +01:00
|
|
|
typeStr (TY const& ref) noexcept
|
2016-01-05 23:34:53 +01:00
|
|
|
{
|
|
|
|
|
return typeStr (&ref);
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-08 08:20:59 +01:00
|
|
|
inline std::string
|
|
|
|
|
typeStr (void const*) noexcept
|
|
|
|
|
{
|
|
|
|
|
return VOID_INDICATOR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** simple expressive symbol to designate a type
|
|
|
|
|
* @return single word identifier, derived from the
|
|
|
|
|
* full type name, not necessarily correct or unique
|
|
|
|
|
*/
|
|
|
|
|
template<typename TY>
|
|
|
|
|
inline std::string
|
|
|
|
|
typeSymbol (TY const* obj =nullptr)
|
|
|
|
|
{
|
|
|
|
|
auto mangledType = obj? typeid(*obj).name()
|
|
|
|
|
: typeid(TY).name();
|
|
|
|
|
return primaryTypeComponent (mangledType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename TY>
|
sanity: how to pass 'anything' properly to the type diagnostics function
turns out this is a tricky situation.
We want to accept pretty mutch everything, yet we want to get a grip
on anything object-like, so to reveal available RTTI information.
Now, given the way C++ template substitution works, the 'TY const&' overload
wins with only a few exceptions. The reason is, C++ invokes most functions
passing the concrete argument as reference, unless this is not possible,
because the concrete artument is a rvalue. The automatic reduction of
reference expressions does the rest. Consequently the overload with 'const&'
turns out to be the best match even when we invoke the function with a
pointer expression, which would then be made into a pointer-to-a pointer
by our forward call.
There are two remedies for this dilemma:
- make the second overload just typeStr (TY&)
- explicitly remove the second overload for pointers
The first solution unfortunately would rule out passing of anonymous
objects like concatenated strings; in fact it would rule out passing
rvalues as such. While the second solution, chosen here, works really
for everything, and also has the nice side effect of stripping away
any const, pointer and reference adornements elegantly before we
even start to analyse the type.
The only downside of this solution is that it looks intimidating
to the casual reader. Well, I'd say, get used to it.
2016-01-09 01:32:02 +01:00
|
|
|
inline disable_if<std::is_pointer<TY>,
|
|
|
|
|
std::string >
|
2016-01-08 08:20:59 +01:00
|
|
|
typeSymbol (TY const& ref)
|
|
|
|
|
{
|
|
|
|
|
return typeSymbol (&ref);
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-08 01:00:05 +01:00
|
|
|
}}// namespace lib::meta
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-01-08 08:20:59 +01:00
|
|
|
|
2016-01-08 01:00:05 +01:00
|
|
|
namespace util {
|
|
|
|
|
|
|
|
|
|
using lib::meta::typeStr;
|
2016-01-08 08:20:59 +01:00
|
|
|
using lib::meta::FAILURE_INDICATOR;
|
2016-01-05 23:34:53 +01:00
|
|
|
|
|
|
|
|
|
2016-01-05 23:52:48 +01:00
|
|
|
/** failsafe invocation of custom string conversion.
|
|
|
|
|
* @return string to represent the object, by default a [type display](\ref typeStr)
|
|
|
|
|
* @remarks this is a lightweight solution to at least _get any human readable string
|
|
|
|
|
* representation for pretty much every language object._ This minimal solution
|
|
|
|
|
* is defined here, to allow for built-in diagnostics for custom types without
|
|
|
|
|
* the danger of creating much header inclusion and code size bloat. A more
|
|
|
|
|
* elaborate, [extended solution](lib::toString), including _lexical conversions
|
|
|
|
|
* for numbers,_ is defined in format-obj.hpp
|
|
|
|
|
* @note any exceptions during string conversion are caught and silently ignored;
|
|
|
|
|
* the returned string indicates "↯" in this case.
|
|
|
|
|
*/
|
|
|
|
|
template<typename X, typename COND =void>
|
2016-01-08 01:00:05 +01:00
|
|
|
struct StringConv
|
2016-01-05 23:52:48 +01:00
|
|
|
{
|
2016-01-06 04:04:56 +01:00
|
|
|
static std::string
|
|
|
|
|
invoke (X const& x) noexcept
|
|
|
|
|
try { return "«"+typeStr(x)+"»"; }
|
2016-01-08 08:20:59 +01:00
|
|
|
catch(...) { return FAILURE_INDICATOR; }
|
2016-01-05 23:52:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<typename X>
|
2016-01-08 01:00:05 +01:00
|
|
|
struct StringConv<X, lib::meta::enable_CustomStringConversion<X>>
|
2016-01-05 23:52:48 +01:00
|
|
|
{
|
|
|
|
|
static std::string
|
2016-01-06 04:04:56 +01:00
|
|
|
invoke (X const& val) noexcept
|
2016-01-05 23:52:48 +01:00
|
|
|
try { return std::string(val); }
|
2016-01-08 08:20:59 +01:00
|
|
|
catch(...) { return FAILURE_INDICATOR; }
|
2016-01-05 23:52:48 +01:00
|
|
|
};
|
2016-01-06 04:04:56 +01:00
|
|
|
|
|
|
|
|
// NOTE: this is meant to be extensible;
|
|
|
|
|
// more specialisations are e.g. in format-obj.hpp
|
|
|
|
|
|
2016-01-08 01:00:05 +01:00
|
|
|
|
|
|
|
|
|
2016-01-06 04:04:56 +01:00
|
|
|
|
|
|
|
|
/** pretty-print a double in fixed-point format */
|
|
|
|
|
std::string showDouble (double) noexcept;
|
|
|
|
|
std::string showFloat (float) noexcept;
|
|
|
|
|
|
2016-01-06 04:51:37 +01:00
|
|
|
/** pretty-print an address as hex-suffix */
|
2016-01-07 20:17:07 +01:00
|
|
|
std::string showAddr (void const* addr) noexcept;
|
2016-01-06 04:04:56 +01:00
|
|
|
|
|
|
|
|
template<typename X>
|
|
|
|
|
inline std::string
|
sanity: how to pass 'anything' properly to the type diagnostics function
turns out this is a tricky situation.
We want to accept pretty mutch everything, yet we want to get a grip
on anything object-like, so to reveal available RTTI information.
Now, given the way C++ template substitution works, the 'TY const&' overload
wins with only a few exceptions. The reason is, C++ invokes most functions
passing the concrete argument as reference, unless this is not possible,
because the concrete artument is a rvalue. The automatic reduction of
reference expressions does the rest. Consequently the overload with 'const&'
turns out to be the best match even when we invoke the function with a
pointer expression, which would then be made into a pointer-to-a pointer
by our forward call.
There are two remedies for this dilemma:
- make the second overload just typeStr (TY&)
- explicitly remove the second overload for pointers
The first solution unfortunately would rule out passing of anonymous
objects like concatenated strings; in fact it would rule out passing
rvalues as such. While the second solution, chosen here, works really
for everything, and also has the nice side effect of stripping away
any const, pointer and reference adornements elegantly before we
even start to analyse the type.
The only downside of this solution is that it looks intimidating
to the casual reader. Well, I'd say, get used to it.
2016-01-09 01:32:02 +01:00
|
|
|
showAddr (X& elm) noexcept
|
2016-01-06 04:04:56 +01:00
|
|
|
{
|
|
|
|
|
return showAddr(&elm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** diagnostics helper for explicitly indicating pointers */
|
|
|
|
|
template<typename X>
|
|
|
|
|
inline std::string
|
|
|
|
|
showPtr (X* ptr =nullptr)
|
|
|
|
|
{
|
2016-01-08 01:00:05 +01:00
|
|
|
return ptr? showAddr(ptr) + " ↗" + StringConv<X>::invoke(*ptr)
|
2016-01-06 04:04:56 +01:00
|
|
|
: "⟂ «" + typeStr(ptr) + "»";
|
|
|
|
|
}
|
2016-01-05 23:52:48 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-01-06 04:04:56 +01:00
|
|
|
}// namespace util
|
|
|
|
|
#endif /*LIB_META_UTIL_H*/
|