format: allow %! specifier for all types

If we allow the %! specifier for all types it greatly simplifies
handling of format strings in templated code, or code with non-trivial
typedefs.
This commit is contained in:
Danny Robson 2016-09-27 15:23:22 +10:00
parent b7d141322c
commit fad44bd1f7
2 changed files with 33 additions and 14 deletions

View File

@ -88,6 +88,11 @@ namespace util { namespace format { namespace detail {
};
///////////////////////////////////////////////////////////////////////////
std::ostream&
operator<< (std::ostream &os, specifier::kind k);
///////////////////////////////////////////////////////////////////////////
// provides the kind, a conversion specifier, and expected length for a
// given type.
@ -609,7 +614,7 @@ namespace util { namespace format { namespace detail {
if (spec.k == specifier::kind::POINTER)
return write (os, spec, reinterpret_cast<const void*> (t));
if (spec.k != specifier::kind::STRING)
if (spec.k != specifier::kind::STRING && spec.k != specifier::kind::OSTREAM)
throw conversion_error ("invalid specifier kind for string argumetn");
const auto len = spec.precision < 0 ? spec.precision :
@ -651,10 +656,10 @@ namespace util { namespace format { namespace detail {
//-------------------------------------------------------------------------
template <typename OutputT>
OutputT
write (OutputT os, const specifier s, const char t)
write (OutputT os, const specifier spec, const char t)
{
if (s.k != specifier::kind::CHARACTER)
throw conversion_error ("invalid specifier kind for char argument");
if (spec.k != specifier::kind::CHARACTER && spec.k != specifier::kind::OSTREAM)
throw conversion_error (render ("invalid specifier kind for char argument: %!", spec.k));
*os = t;
return ++os;
@ -692,7 +697,7 @@ namespace util { namespace format { namespace detail {
>
write (OutputT &os, const specifier &spec, const T t)
{
if (spec.k != specifier::kind::POINTER)
if (spec.k != specifier::kind::POINTER && spec.k != specifier::kind::OSTREAM)
throw conversion_error ("invalid conversion specifier for pointer value");
// glibc at least uses a special form for null pointers
@ -730,15 +735,18 @@ namespace util { namespace format { namespace detail {
>
write (OutputT os, const specifier spec, ValueT t)
{
if (spec.k == specifier::kind::POINTER && !t)
{
if (spec.k == specifier::kind::POINTER && !t) {
return write (os, spec, reinterpret_cast<void*> (t));
}
if (spec.k != (std::is_unsigned<ValueT>::value ? specifier::kind::UNSIGNED : specifier::kind::SIGNED))
throw conversion_error ("invalid conversion specifier for integer value");
if (!(spec.k == specifier::kind::UNSIGNED && std::is_unsigned<ValueT>::value ||
spec.k == specifier::kind::SIGNED && std::is_signed <ValueT>::value ||
spec.k == specifier::kind::OSTREAM))
{
throw conversion_error ("invalid conversion specifier for integer");
}
if (sizeof (ValueT) > spec.length)
if (sizeof (ValueT) > spec.length && spec.k != specifier::kind::OSTREAM)
throw length_error ("overlength value parameter");
const auto numerals = digits (t, spec.base);
@ -801,8 +809,13 @@ namespace util { namespace format { namespace detail {
std::is_floating_point<T>::value,
OutputT
>
write (OutputT os, const specifier spec, T t)
write (OutputT os, specifier spec, T t)
{
if (spec.k == specifier::kind::OSTREAM) {
spec = specifier {};
spec.k = specifier::kind::REAL;
}
if (spec.k != specifier::kind::REAL)
throw conversion_error ("invalid conversion specifier for real value");
@ -820,7 +833,8 @@ namespace util { namespace format { namespace detail {
if (spec.left_adjusted) *cursor++ = '-';
if (spec.positive_char) *cursor++ = spec.positive_char;
cursor += sprintf (cursor, "%u", spec.width);
if (spec.width)
cursor += sprintf (cursor, "%u", spec.width);
if (spec.precision >= 0) {
*cursor++ = '.';

View File

@ -55,6 +55,7 @@ main (void)
CHECK_RENDER ("%ju", "1", (uintmax_t)1);
CHECK_RENDER ("%zu", "0", (size_t)0);
CHECK_RENDER ("%zu", "1", (size_t)1);
CHECK_RENDER ("%!", "1", 1u);
CHECK_RENDER ("%o", "1", 01u);
CHECK_RENDER ("%o", "13", 013u);
@ -122,7 +123,10 @@ main (void)
CHECK_RENDER ("%3.2f", "1.23", 1.2345678);
CHECK_RENDER ("%3.2f", "1234.57", 1234.5678);
CHECK_RENDER ("%!", "1", 1.);
CHECK_RENDER ("%c", "A", 'A');
CHECK_RENDER ("%!", "A", 'A');
CHECK_RENDER ("%s", "foo", "foo");
CHECK_RENDER ("%s", "foo", std::string ("foo"));
@ -134,6 +138,8 @@ main (void)
CHECK_RENDER ("%.64s", "foo", "foo");
CHECK_RENDER ("%3.1s", " f", "foo");
CHECK_RENDER ("%-3.1s", "f ", "foo");
CHECK_RENDER ("%!", "foo", "foo");
CHECK_RENDER ("%!", "userobj", userobj {});
CHECK_RENDER ("%p", "0x1234567", (void*)0x01234567);
@ -141,6 +147,7 @@ main (void)
CHECK_RENDER ("%p", "0x1234567", (char*)0x01234567);
CHECK_RENDER ("%p", "(nil)", nullptr);
CHECK_RENDER ("%p", "(nil)", NULL);
CHECK_RENDER ("%!", "0x1234567", (void*)0x01234567);
CHECK_RENDER ("%%", "%");
CHECK_RENDER ("%10%", "%");
@ -202,6 +209,4 @@ main (void)
CHECK_THROW("%c", conversion_error, 1u);
CHECK_THROW("%c", conversion_error, "foo");
CHECK_THROW("%!", conversion_error, 1u);
}