Note: not fixing all relevant warnings. Especially, the "-Woverloaded-virtual" of Clang defeats the whole purpose of generated generic interfaces. For example, our Variant type is instantiated with a list of types the variant can hold. Through metaprogramming, this instantiation generates also an embedded Visitor interface, which has virtual 'handle(TY)' functions for all the types in question The client now may implement, or even partially implement this Visitor, to retrieve specific data out of given Variant instance with unknown conent. To complain that some other virtual overload is now shaddowed is besides the point, so we might consider to disable this warning altogether
446 lines
14 KiB
C++
446 lines
14 KiB
C++
/*
|
|
FORMAT-STRING.hpp - string template formatting based on boost::format
|
|
|
|
Copyright (C) Lumiera.org
|
|
2011, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program 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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
/** @file format-string.hpp
|
|
** Front-end for printf-style string template interpolation.
|
|
** While the actual implementation just delegates to boost::format, this front-end
|
|
** hides the direct dependency, additionally invokes a custom toSting conversion
|
|
** whenever possible, provides a direct automatic conversion to the formatted result
|
|
** to string and catches any exceptions.
|
|
**
|
|
** This front-end is used pervasively for diagnostics and logging, so keeping down the
|
|
** compilation and object size cost and reliably handling any error is more important
|
|
** than the (small) performance gain of directly invoking boost::format (which is
|
|
** known to be 10 times slower than printf anyway).
|
|
**
|
|
** \par Implementation notes
|
|
** To perform the formatting, usually a \c _Fmt object is created as an anonymous
|
|
** temporary, but it may as well be stored into a variable. Copying is not permitted.
|
|
** Individual parameters are then fed for formatting through the \c '%' operator.
|
|
** Each instance of _Fmt uses its own embedded boost::format object for implementation,
|
|
** but this formatter resides within an opaque buffer embedded into the frontend object.
|
|
** The rationale for this admittedly tricky approach is to confine any usage of boost::format
|
|
** to the implementation translation unit (format-string.cpp).
|
|
**
|
|
** The implementation is invoked by the frontend through a set of explicit specialisations
|
|
** for all the relevant \em primitive data types. For custom types, we prefer to invoke
|
|
** operator string() if possible, which is determined by a simple metaprogramming test,
|
|
** which is defined in lib/meta/util.pp, without relying on boost. As a fallback, for
|
|
** all other types without built-in or custom string conversion, we use the mangled
|
|
** type string produced by RTTI.
|
|
**
|
|
** The compile time and object size overhead incurred by using this header was verified
|
|
** to be negligible, in comparison to using boost::format. When compiling a demo example
|
|
** on x86_64, the following executable sizes could be observed:
|
|
**
|
|
** debug stripped
|
|
** just string concatenation ............... 42k 8.8k
|
|
** including and using format-string.hpp ... 50k 9.4k
|
|
** including and using boost::format ....... 420k 140k
|
|
**
|
|
** In addition, we need to take the implementation translation unit (format-string.cpp)
|
|
** into account, which is required once per application and contains the specialisations
|
|
** for all primitive types. In the test showed above, the corresponding object file
|
|
** had a size of 1300k (with debug information) resp. 290k (stripped).
|
|
**
|
|
** \par Usage
|
|
** The syntax of the format string is defined by boost::format and closely mimics
|
|
** the printf formatting directives. The notable difference is that boost::format
|
|
** uses the C++ stream output framework, and thus avoiding the perils of printf.
|
|
** The individual formatting placeholders just set the corresponding flags on
|
|
** an embedded string stream, thus the actual parameter types cause the
|
|
** selection of a suitable format, not the definitions within the
|
|
** format string.
|
|
**
|
|
** An illegal format string will raise an error::Fatal. Any other error during usage of
|
|
** the formatter is caught, logged and suppressed, inserting an error indicator into
|
|
** the formatted result instead
|
|
**
|
|
** A formatter is usually created as an anonymous object, at places where a string
|
|
** is expected. An arbitrary number of parameters is then supplied using the \c '%' operator.
|
|
** The result can be obtained
|
|
** - by string conversion
|
|
** - by feeding into an output stream.
|
|
**
|
|
** Code example:
|
|
** \code
|
|
** double total = 22.9499;
|
|
** const char * currency = "€";
|
|
** cout << _Fmt("price %+5.2f %s") % total % currency << endl;
|
|
** \endcode
|
|
**
|
|
** @remarks See the unit-test for extensive usage examples and corner cases.
|
|
** The header format-util.hpp provides an alternative string conversion,
|
|
** using a bit of boost type traits and lexical_cast, but no boost::format.
|
|
** @warning not suited for performance critical code. About 10 times slower than printf.
|
|
**
|
|
** @see FormatString_test
|
|
** @see format-util.hpp
|
|
**
|
|
*/
|
|
|
|
|
|
#ifndef UTIL_FORMAT_STRING_H
|
|
#define UTIL_FORMAT_STRING_H
|
|
|
|
#include "lib/error.hpp"
|
|
#include "lib/meta/util.hpp"
|
|
#include "lib/meta/size-trait.hpp"
|
|
|
|
#include <string>
|
|
#include <typeinfo>
|
|
#include <boost/noncopyable.hpp>
|
|
#include <boost/utility/enable_if.hpp>
|
|
|
|
|
|
|
|
namespace std { // forward declaration to avoid including <iostream>
|
|
|
|
template<typename C>
|
|
struct char_traits;
|
|
|
|
template<typename C, class _TRAITS>
|
|
class basic_ostream;
|
|
|
|
typedef basic_ostream<char, char_traits<char> > ostream;
|
|
|
|
}
|
|
|
|
namespace lib {
|
|
class Literal;
|
|
class Symbol;
|
|
}
|
|
|
|
|
|
namespace util {
|
|
|
|
using std::string;
|
|
using boost::enable_if;
|
|
|
|
typedef unsigned char uchar;
|
|
|
|
LUMIERA_ERROR_DECLARE (FORMAT_SYNTAX); ///< "Syntax error in format string for boost::format"
|
|
|
|
|
|
|
|
/**
|
|
* A front-end for using printf-style formatting.
|
|
* Values to be formatted can be supplied through the
|
|
* operator%. Custom defined string conversions on objects
|
|
* will be used, any errors while invoking the format operation
|
|
* will be suppressed. The implementation is based on boost::format,
|
|
* but kept opaque to keep code size and compilation times down.
|
|
* @see FormatString_test
|
|
*/
|
|
class _Fmt
|
|
: boost::noncopyable
|
|
{
|
|
/** size of an opaque implementation Buffer */
|
|
enum{ FORMATTER_SIZE = lib::meta::SizeTrait::BOOST_FORMAT };
|
|
|
|
typedef char Implementation[FORMATTER_SIZE];
|
|
|
|
|
|
/** @internal buffer to hold a boost::format */
|
|
mutable Implementation formatter_;
|
|
|
|
|
|
template<typename VAL>
|
|
static void format (const VAL, Implementation&);
|
|
|
|
/** helper to prepare parameters for inclusion */
|
|
template<typename VAL, class SEL =void>
|
|
struct Converter;
|
|
|
|
|
|
|
|
public:
|
|
~_Fmt ();
|
|
_Fmt (string formatString);
|
|
|
|
operator string() const; ///< get the formatted result
|
|
|
|
template<typename VAL>
|
|
_Fmt&
|
|
operator% (VAL const&);
|
|
|
|
|
|
friend std::ostream&
|
|
operator<< (std::ostream& os, _Fmt const&);
|
|
|
|
friend bool operator== (_Fmt const&, _Fmt const&);
|
|
friend bool operator== (_Fmt const&, string const&);
|
|
friend bool operator== (_Fmt const&, const char * const);
|
|
friend bool operator== (string const& , _Fmt const&);
|
|
friend bool operator== (const char * const, _Fmt const&);
|
|
|
|
template<typename X>
|
|
friend bool operator != (_Fmt const& fmt, X const& x) { return !(fmt == x); }
|
|
template<typename X>
|
|
friend bool operator != (X const& x, _Fmt const& fmt) { return !(x == fmt); }
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== forwarding into the implementation ====== */
|
|
|
|
/** The percent operator (\c '%' ) is used do feed parameter values
|
|
* to be included into the formatted result, at the positions marked
|
|
* by printf-style placeholders within the format string.
|
|
*
|
|
* \par type specific treatment
|
|
* Basic types (numbers, chars, strings) are passed to the implementation
|
|
* (= boost::format) literally. For custom types, we try to use a custom
|
|
* string conversion, if applicable. Non-NULL pointers will be dereferenced,
|
|
* with the exception of C-Strings and \c void*. Any other type gets just
|
|
* translated into a type-ID (using the mangled RTTI info). In case of errors
|
|
* during the conversion, a string representation of the error is returned
|
|
* @param val arbitrary value or pointer to be included into the result
|
|
* @warning you need to provide exactly the right number of parameters,
|
|
* i.e. matching the number of fields in the format string.
|
|
* @note EX_FREE
|
|
*/
|
|
template<typename VAL>
|
|
inline _Fmt&
|
|
_Fmt::operator% (VAL const& val)
|
|
{
|
|
Converter<VAL>::dump (val, formatter_);
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
namespace { // helpers to pick a suitable specialisation....
|
|
|
|
/**
|
|
* by default we don't allow to
|
|
* treat any types directly by boost::format.
|
|
* As fallback we rather just produce a type-ID
|
|
*/
|
|
template<typename X>
|
|
struct _allow_call { enum{ value = false };};
|
|
|
|
/* the following definitions enable some primitive types
|
|
* to be handed over to the boost::format implementation */
|
|
template<> struct _allow_call<string> { enum{ value = true }; };
|
|
template<> struct _allow_call<char> { enum{ value = true }; };
|
|
template<> struct _allow_call<uchar> { enum{ value = true }; };
|
|
template<> struct _allow_call<int16_t> { enum{ value = true }; };
|
|
template<> struct _allow_call<uint16_t>{ enum{ value = true }; };
|
|
template<> struct _allow_call<int32_t> { enum{ value = true }; };
|
|
template<> struct _allow_call<uint32_t>{ enum{ value = true }; };
|
|
template<> struct _allow_call<int64_t> { enum{ value = true }; };
|
|
template<> struct _allow_call<uint64_t>{ enum{ value = true }; };
|
|
template<> struct _allow_call<float> { enum{ value = true }; };
|
|
template<> struct _allow_call<double> { enum{ value = true }; };
|
|
#ifndef __x86_64__
|
|
template<> struct _allow_call<long> { enum{ value = true }; };
|
|
template<> struct _allow_call<ulong> { enum{ value = true }; };
|
|
#endif
|
|
|
|
template<typename X>
|
|
struct _shall_format_directly
|
|
{
|
|
typedef typename lib::meta::UnConst<X>::Type BaseType;
|
|
|
|
enum{ value = _allow_call<BaseType>::value };
|
|
};
|
|
|
|
|
|
|
|
template<typename X>
|
|
struct _shall_convert_toString
|
|
{
|
|
enum{ value = ! _shall_format_directly<X>::value
|
|
&& lib::meta::can_convertToString<X>::value
|
|
};
|
|
};
|
|
|
|
|
|
inline void
|
|
_clear_errorflag()
|
|
{
|
|
const char* errID = lumiera_error();
|
|
TRACE_IF (errID, progress, "Lumiera errorstate '%s' cleared.", errID);
|
|
}
|
|
|
|
inline string
|
|
_log_and_stringify (std::exception const& ex)
|
|
{
|
|
_clear_errorflag();
|
|
WARN (progress, "Error while invoking custom string conversion: %s", ex.what());
|
|
try {
|
|
return string("<string conversion failed: ")+ex.what()+">";
|
|
}
|
|
catch(...) { /* secondary errors ignored */ }
|
|
return "(formatting failure)";
|
|
}
|
|
|
|
inline string
|
|
_log_unknown_exception()
|
|
{
|
|
const char* errID = lumiera_error();
|
|
if (errID)
|
|
ERROR (progress, "Unknown error while invoking custom string conversion. Lumiera error flag = %s", errID);
|
|
else
|
|
ERROR (progress, "Unknown error while invoking custom string conversion. No Lumiera error flag set.");
|
|
return "<Unknown error in string conversion>";
|
|
}
|
|
|
|
}//(End) guards/helpers
|
|
|
|
|
|
|
|
|
|
/* === explicit specialisations to control the kind of conversion === */
|
|
|
|
/** default/fallback: just indicate the (static) type */
|
|
template<typename VAL, class SEL>
|
|
struct _Fmt::Converter
|
|
{
|
|
static void
|
|
dump (VAL const&, Implementation& impl)
|
|
{
|
|
format (string("«")+typeid(VAL).name()+"»", impl);
|
|
}
|
|
};
|
|
|
|
template<typename VAL>
|
|
struct _Fmt::Converter<VAL*>
|
|
{
|
|
static void
|
|
dump (const VAL *pVal, Implementation& impl)
|
|
{
|
|
if (pVal)
|
|
Converter<VAL>::dump(*pVal, impl);
|
|
else
|
|
format ("<null>", impl);
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct _Fmt::Converter<void *>
|
|
{
|
|
static void
|
|
dump (const void* address, Implementation& impl)
|
|
{
|
|
format (address, impl);
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct _Fmt::Converter<const char *>
|
|
{
|
|
static void
|
|
dump (const char* cString, Implementation& impl)
|
|
{
|
|
format (cString? cString : "↯", impl);
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct _Fmt::Converter<lib::Literal>
|
|
{
|
|
static void
|
|
dump (lib::Literal const& literal, Implementation& impl)
|
|
{
|
|
format (literal.empty()? "" : literal.c(), impl);
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct _Fmt::Converter<lib::Symbol>
|
|
: _Fmt::Converter<lib::Literal>
|
|
{ };
|
|
|
|
/** some custom types explicitly provide a string representation */
|
|
template<typename VAL>
|
|
struct _Fmt::Converter<VAL, typename enable_if< _shall_convert_toString<VAL> >::type>
|
|
{
|
|
static void
|
|
dump (VAL const& val, Implementation& impl)
|
|
try {
|
|
format (string(val), impl);
|
|
}
|
|
catch(std::exception const& ex)
|
|
{
|
|
format (_log_and_stringify(ex), impl);
|
|
}
|
|
catch(...)
|
|
{
|
|
format (_log_unknown_exception(), impl);
|
|
}
|
|
};
|
|
|
|
/** some basic types are directly forwarded down to the implementation;
|
|
* @note this requires explicit specialisations in format-string.cpp */
|
|
template<typename VAL>
|
|
struct _Fmt::Converter<VAL, typename enable_if< _shall_format_directly<VAL> >::type>
|
|
{
|
|
static void
|
|
dump (const VAL val, Implementation& impl)
|
|
{
|
|
format (val, impl);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/* === comparison of formatter objects === */
|
|
|
|
inline bool
|
|
operator== (_Fmt const& left, _Fmt const& right)
|
|
{
|
|
return string(left) == string(right);
|
|
}
|
|
|
|
inline bool
|
|
operator== (_Fmt const& fmt, string const& str)
|
|
{
|
|
return string(fmt) == str;
|
|
}
|
|
|
|
inline bool
|
|
operator== (_Fmt const& fmt, const char * const cString)
|
|
{
|
|
return string(fmt) == string(cString);
|
|
}
|
|
|
|
inline bool
|
|
operator== (string const& str, _Fmt const& fmt)
|
|
{
|
|
return fmt == str;
|
|
}
|
|
|
|
inline bool
|
|
operator== (const char * const cString, _Fmt const& fmt)
|
|
{
|
|
return fmt == cString;
|
|
}
|
|
|
|
|
|
|
|
} // namespace util
|
|
#endif
|