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

View File

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