diff --git a/src/include/CMakeLists.txt b/src/include/CMakeLists.txt index 34fe181df7..4705673583 100644 --- a/src/include/CMakeLists.txt +++ b/src/include/CMakeLists.txt @@ -4,7 +4,7 @@ set (public_headers argparse.h color.h dassert.h errorhandler.h export.h imagecache.h imageio.h optparser.h osdep.h paramlist.h plugin.h refcnt.h strutil.h sysutil.h texture.h thread.h timer.h - typedesc.h ustring.h varyingref.h + tinyformat.h typedesc.h ustring.h varyingref.h ) if (NOT USE_EXTERNAL_PUGIXML) diff --git a/src/include/argparse.h b/src/include/argparse.h index 67df75c4fa..3cb244a444 100644 --- a/src/include/argparse.h +++ b/src/include/argparse.h @@ -47,14 +47,7 @@ #include "export.h" #include "version.h" - -#ifndef OPENIMAGEIO_PRINTF_ARGS /* See comments in strutil.h */ -# ifndef __GNUC__ -# define __attribute__(x) -# endif -# define OPENIMAGEIO_PRINTF_ARGS(fmtarg_pos, vararg_pos) \ - __attribute__ ((format (printf, fmtarg_pos, vararg_pos) )) -#endif +#include "tinyformat.h" OIIO_NAMESPACE_ENTER @@ -191,7 +184,10 @@ class DLLPUBLIC ArgParse { std::vector m_option; ArgOption *find_option(const char *name); - void error (const char *format, ...) OPENIMAGEIO_PRINTF_ARGS(2,3); + // void error (const char *format, ...) + TINYFORMAT_WRAP_FORMAT (void, error, /**/, + std::ostringstream msg;, msg, m_errmessage = msg.str();) + int found (const char *option); // number of times option was parsed }; diff --git a/src/include/errorhandler.h b/src/include/errorhandler.h index ba1ef164e9..7912f89ae1 100644 --- a/src/include/errorhandler.h +++ b/src/include/errorhandler.h @@ -36,14 +36,8 @@ #include "export.h" #include "version.h" +#include "strutil.h" -#ifndef OPENIMAGEIO_PRINTF_ARGS /* See comments in strutil.h */ -# ifndef __GNUC__ -# define __attribute__(x) -# endif -# define OPENIMAGEIO_PRINTF_ARGS(fmtarg_pos, vararg_pos) \ - __attribute__ ((format (printf, fmtarg_pos, vararg_pos) )) -#endif OIIO_NAMESPACE_ENTER { diff --git a/src/include/imageio.h b/src/include/imageio.h index 87ef37f53d..63a2a41ab8 100644 --- a/src/include/imageio.h +++ b/src/include/imageio.h @@ -716,14 +716,17 @@ class DLLPUBLIC ImageInput { protected: /// Error reporting for the plugin implementation: call this with - /// printf-like arguments. - void error (const char *format, ...) const OPENIMAGEIO_PRINTF_ARGS(2,3); + /// printf-like arguments. Note however that this is fully typesafe! + // void error (const char *format, ...) const; + TINYFORMAT_WRAP_FORMAT (void, error, const, + std::ostringstream msg;, msg, append_error(msg.str());) protected: ImageSpec m_spec; // format spec of the current open subimage/MIPlevel private: mutable std::string m_errmessage; // private storage of error message + void append_error (const std::string& message) const; // add to m_errmessage static ImageInput *create (const std::string &filename, bool do_open, const std::string &plugin_searchpath); @@ -959,8 +962,10 @@ class DLLPUBLIC ImageOutput { protected: /// Error reporting for the plugin implementation: call this with - /// printf-like arguments. - void error (const char *format, ...) OPENIMAGEIO_PRINTF_ARGS(2,3); + /// printf-like arguments. Note however that this is fully typesafe! + // void error (const char *format, ...) + TINYFORMAT_WRAP_FORMAT (void, error, const, + std::ostringstream msg;, msg, append_error(msg.str());) /// Helper routines used by write_* implementations: convert data (in /// the given format and stride) to the "native" format of the file @@ -988,6 +993,7 @@ class DLLPUBLIC ImageOutput { ImageSpec m_spec; ///< format spec of the currently open image private: + void append_error (const std::string& message) const; // add to m_errmessage mutable std::string m_errmessage; ///< private storage of error message }; diff --git a/src/include/strutil.h b/src/include/strutil.h index ebe482989e..77c8e2f48f 100644 --- a/src/include/strutil.h +++ b/src/include/strutil.h @@ -49,6 +49,8 @@ #include "export.h" #include "version.h" +#include "tinyformat.h" + #ifndef OPENIMAGEIO_PRINTF_ARGS # ifndef __GNUC__ # define __attribute__(x) @@ -76,11 +78,16 @@ OIIO_NAMESPACE_ENTER /// @brief String-related utilities. namespace Strutil { - -/// Return a std::string formatted from printf-like arguments. +/// Construct a std::string in a printf-like fashion. In other words, +/// something like: +/// std::string s = Strutil::format ("blah %d %g", (int)foo, (float)bar); +/// +/// The printf argument list is fully typesafe via tinyformat; format +/// conceptually has the signature /// -std::string DLLPUBLIC format (const char *fmt, ...) - OPENIMAGEIO_PRINTF_ARGS(1,2); +/// static std::string Strutil::format (const char *fmt, ...); +TINYFORMAT_WRAP_FORMAT (static std::string, format, /**/, + std::ostringstream msg;, msg, return msg.str();) /// Return a std::string formatted from printf-like arguments -- passed /// already as a va_list. diff --git a/src/include/tinyformat.h b/src/include/tinyformat.h new file mode 100644 index 0000000000..319138e853 --- /dev/null +++ b/src/include/tinyformat.h @@ -0,0 +1,926 @@ +// tinyformat.h +// Copyright (C) 2011, Chris Foster [chris42f (at) gmail (d0t) com] +// +// Boost Software License - Version 1.0 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//------------------------------------------------------------------------------ +// Tinyformat: A minimal type safe printf-replacement library for C++ +// +// This library aims to support 95% of casual C++ string formatting needs with +// a single lightweight header file. Anything you can do with this library +// can also be done with the standard C++ streams, but probably with +// considerably more typing :) +// +// Design goals: +// +// * Simplicity and minimalism. A single header file to include and distribute +// with your own projects. +// * Type safety and extensibility for user defined types. +// * Parse standard C99 format strings, and support most features. +// * Support as many commonly used ``printf()`` features as practical without +// compromising on simplicity. +// +// +// Example usage +// ------------- +// +// To print the date, we might have +// +// std::string weekday = "Wednesday"; +// const char* month = "July"; +// long day = 27; +// int hour = 14; +// int min = 44; +// +// tfm::format(std::cout, "%s, %s %d, %.2d:%.2d\n", +// weekday, month, day, hour, min); +// +// (The types here are intentionally odd to emphasize the type safety of the +// interface.) The same thing could be achieved using either of the two +// convenience functions. One returns a std::string: +// +// std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n", +// weekday, month, day, hour, min); +// std::cout << date; +// +// The other prints to the std::cout stream: +// +// tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min); +// +// +// Brief outline of functionality +// ------------------------------ +// +// (For full docs, see the accompanying README) +// +// +// Interface functions: +// +// template +// void format(std::ostream& stream, const char* formatString, +// const T1& value1, const T2& value1, ...) +// +// template +// std::string format(const char* formatString, +// const T1& value1, const T2& value1, ...) +// +// template +// void printf(const char* formatString, +// const T1& value1, const T2& value1, ...) +// +// +// Error handling: Define TINYFORMAT_ERROR to customize the error handling for +// format strings which are unsupported or have the wrong number of format +// specifiers (calls assert() by default). +// +// User defined types: Uses operator<< for user defined types by default. +// Overload formatValue() or formatValueBasic() for more control. +// +// Wrapping tfm::format inside a user defined format function: See the macros +// TINYFORMAT_WRAP_FORMAT and TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS. + + + +#ifndef TINYFORMAT_H_INCLUDED +#define TINYFORMAT_H_INCLUDED + +#include +#include +#include + +namespace tinyformat {} +//------------------------------------------------------------------------------ +// Config section. Customize to your liking! + +// Namespace alias to encourage brevity +namespace tfm = tinyformat; + +// Error handling; calls assert() by default. +// #define TINYFORMAT_ERROR(reasonString) your_error_handler(reasonString) + +// Define for C++0x variadic templates which make the code shorter & more +// general. If you don't define this, C++0x support is autodetected below. +// #define TINYFORMAT_USE_VARIADIC_TEMPLATES + + +//------------------------------------------------------------------------------ +// Implementation details. +#ifndef TINYFORMAT_ERROR +# define TINYFORMAT_ERROR(reason) assert(0 && reason) +#endif + +#if !defined(TINYFORMAT_USE_VARIADIC_TEMPLATES) && !defined(TINYFORMAT_NO_VARIADIC_TEMPLATES) +# ifdef __GXX_EXPERIMENTAL_CXX0X__ +# define TINYFORMAT_USE_VARIADIC_TEMPLATES +# endif +#endif + +#ifdef __GNUC__ +# define TINYFORMAT_NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) +# define TINYFORMAT_NOINLINE __declspec(noinline) +#else +# define TINYFORMAT_NOINLINE +#endif + + +namespace tinyformat { + +//------------------------------------------------------------------------------ +namespace detail { + +// Parse and return an integer from the string c, as atoi() +// On return, c is set to one past the end of the integer. +inline int parseIntAndAdvance(const char*& c) +{ + int i = 0; + for(;*c >= '0' && *c <= '9'; ++c) + i = 10*i + (*c - '0'); + return i; +} + + +// Flags for features not representable with standard stream state +enum ExtraFormatFlags +{ + Flag_TruncateToPrecision = 1<<0, // truncate length to stream precision() + Flag_SpacePadPositive = 1<<1, // pad positive values with spaces +}; + + +// Parse the format string and set the stream state accordingly. +// +// The format mini-language recognized here is meant to be the one from C99, +// with the form "%[flags][width][.precision][length]type". +// +// The return value is a bitwise combination of values from the +// ExtraFormatFlags enum, containing formatting options which can't be natively +// represented using the ostream state. +inline unsigned int streamStateFromFormat(std::ostream& out, + const char* fmtStart, + const char* fmtEnd) +{ + // Reset stream state to defaults. + out.width(0); + out.precision(6); + out.fill(' '); + // Reset most flags; ignore irrelevant unitbuf & skipws. + out.unsetf(std::ios::adjustfield | std::ios::basefield | + std::ios::floatfield | std::ios::showbase | std::ios::boolalpha | + std::ios::showpoint | std::ios::showpos | std::ios::uppercase); + unsigned int extraFlags = 0; + bool precisionSet = false; + bool widthSet = false; + const char* c = fmtStart; + // 1) Parse flags + for(;; ++c) + { + switch(*c) + { + case '#': + out.setf(std::ios::showpoint | std::ios::showbase); + continue; + case '0': + // overridden by left alignment ('-' flag) + if(!(out.flags() & std::ios::left)) + { + // Use internal padding so that numeric values are + // formatted correctly, eg -00010 rather than 000-10 + out.fill('0'); + out.setf(std::ios::internal, std::ios::adjustfield); + } + continue; + case '-': + out.fill(' '); + out.setf(std::ios::left, std::ios::adjustfield); + continue; + case ' ': + // overridden by show positive sign, '+' flag. + if(!(out.flags() & std::ios::showpos)) + extraFlags |= Flag_SpacePadPositive; + continue; + case '+': + out.setf(std::ios::showpos); + extraFlags &= ~Flag_SpacePadPositive; + continue; + } + break; + } + // 2) Parse width + if(*c >= '0' && *c <= '9') + { + widthSet = true; + out.width(parseIntAndAdvance(c)); + } + if(*c == '*') + TINYFORMAT_ERROR("tinyformat: variable field widths not supported"); + // 3) Parse precision + if(*c == '.') + { + ++c; + if(*c == '*') + TINYFORMAT_ERROR("tinyformat: variable precision not supported"); + int precision = 0; + if(*c >= '0' && *c <= '9') + precision = parseIntAndAdvance(c); + else if(*c == '-') // negative precisions ignored, treated as zero. + parseIntAndAdvance(++c); + out.precision(precision); + precisionSet = true; + } + // 4) Ignore any C99 length modifier + while(*c == 'l' || *c == 'h' || *c == 'L' || + *c == 'j' || *c == 'z' || *c == 't') + ++c; + // 5) We're up to the conversion specifier character. + char type = 's'; + if(c < fmtEnd) + type = *c; + // Set stream flags based on conversion specifier (thanks to the + // boost::format class for forging the way here). + bool intConversion = false; + switch(type) + { + case 'u': case 'd': case 'i': + out.setf(std::ios::dec, std::ios::basefield); + intConversion = true; + break; + case 'o': + out.setf(std::ios::oct, std::ios::basefield); + intConversion = true; + break; + case 'X': + out.setf(std::ios::uppercase); + case 'x': case 'p': + out.setf(std::ios::hex, std::ios::basefield); + intConversion = true; + break; + case 'E': + out.setf(std::ios::uppercase); + case 'e': + out.setf(std::ios::scientific, std::ios::floatfield); + out.setf(std::ios::dec, std::ios::basefield); + break; + case 'F': + out.setf(std::ios::uppercase); + case 'f': + out.setf(std::ios::fixed, std::ios::floatfield); + break; + case 'G': + out.setf(std::ios::uppercase); + case 'g': + out.setf(std::ios::dec, std::ios::basefield); + // As in boost::format, let stream decide float format. + out.flags(out.flags() & ~std::ios::floatfield); + break; + case 'a': case 'A': + break; // C99 hexadecimal floating point?? punt! + case 'c': + // Handled as special case inside formatValue() + break; + case 's': + if(precisionSet) + extraFlags |= Flag_TruncateToPrecision; + // Make %s print booleans as "true" and "false" + out.setf(std::ios::boolalpha); + break; + case 'n': + // Not supported - will cause problems! + TINYFORMAT_ERROR("tinyformat: %n conversion spec not supported"); + break; + } + if(intConversion && precisionSet && !widthSet) + { + // "precision" for integers gives the minimum number of digits (to be + // padded with zeros on the left). This isn't really supported by the + // iostreams, but we can approximately simulate it with the width if + // the width isn't otherwise used. + out.width(out.precision()); + out.setf(std::ios::internal, std::ios::adjustfield); + out.fill('0'); + } + // we shouldn't be past the end, though we may equal it if the input + // format was broken and ended with '\0'. + assert(c <= fmtEnd); + return extraFlags; +} + + +// Print literal part of format string and return next format spec position. +// +// Skips over any occurrences of '%%', printing a literal '%' to the output. +// The position of the first non-'%' character of the next format spec is +// returned, or the end of string. +inline const char* printFormatStringLiteral(std::ostream& out, const char* fmt) +{ + const char* c = fmt; + for(; *c != '\0'; ++c) + { + if(*c == '%') + { + out.write(fmt, static_cast(c - fmt)); + fmt = ++c; + if(*c != '%') + return c; + // for '%%' the required '%' will be tacked onto the next section. + } + } + out.write(fmt, static_cast(c - fmt)); + return c; +} + + +// Skip to end of format spec & return it. fmt is expected to point to the +// character after the '%' in the spec. +inline const char* findFormatSpecEnd(const char* fmt) +{ + // Advance to end of format specifier. + const char* c = fmt; + if(*c == '\0') + TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string"); + for(; *c != '\0'; ++c) + { + // For compatibility with C, argument length modifiers don't terminate + // the format + if(*c == 'l' || *c == 'h' || *c == 'L' || + *c == 'j' || *c == 'z' || *c == 't') + continue; + // ... but for generality any other upper or lower case letter does + if((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z')) + return c+1; + } + TINYFORMAT_ERROR("tinyformat: Conversion spec incorrectly terminated by end of string"); + return c; +} + + +// Test whether type T1 is convertible to type T2 +template +struct is_convertible +{ + private: + // two types of different size + struct fail { char dummy[2]; }; + struct succeed { char dummy; }; + // Try to convert a T1 to a T2 by plugging into tryConvert + static fail tryConvert(...); + static succeed tryConvert(const T2&); + static const T1& makeT1(); + public: +# ifdef _MSC_VER + // Disable spurious loss of precision warning in tryConvert(makeT1()) +# pragma warning(push) +# pragma warning(disable:4244) +# endif + // Standard trick: the (...) version of tryConvert will be chosen from + // the overload set only if the version taking a T2 doesn't match. + // Then we compare the sizes of the return types to check which + // function matched. Very neat, in a disgusting kind of way :) + static const bool value = + sizeof(tryConvert(makeT1())) == sizeof(succeed); +# ifdef _MSC_VER +# pragma warning(pop) +# endif +}; + + +// Format the value by casting to type fmtT. This default implementation +// should never be called. +template +struct formatValueAsType +{ + static void invoke(std::ostream& out, const T& value) { assert(0); } +}; +// Specialized version for types that can actually be converted to fmtT, as +// indicated by the "convertible" template parameter. +template +struct formatValueAsType +{ + static void invoke(std::ostream& out, const T& value) + { out << static_cast(value); } +}; + + +// Format at most truncLen characters of a C string to the given stream. +// Return true if formatting proceeded (generic version always returns false) +template +inline bool formatCStringTruncate(std::ostream& out, const T& value, + std::streamsize truncLen) +{ + return false; +} +#define TINYFORMAT_DEFINE_FORMAT_C_STRING_TRUNCATE(type) \ +inline bool formatCStringTruncate(std::ostream& out, type* value, \ + std::streamsize truncLen) \ +{ \ + std::streamsize len = 0; \ + while(len < truncLen && value[len] != 0) \ + ++len; \ + out.write(value, len); \ + return true; \ +} +// Overload for const char* and char*. Could overload for signed & unsigned +// char too, but these are technically unneeded for printf compatibility. +TINYFORMAT_DEFINE_FORMAT_C_STRING_TRUNCATE(const char) +TINYFORMAT_DEFINE_FORMAT_C_STRING_TRUNCATE(char) +#undef TINYFORMAT_DEFINE_FORMAT_C_STRING_TRUNCATE + +} // namespace detail + + +//------------------------------------------------------------------------------ +// Variable formatting functions. May be overridden for user-defined types if +// desired. + + +// Format a value into a stream. Called from format() for all types by default. +// +// Users may override this for their own types. When this function is called, +// the stream flags will have been modified according to the format string. +// The format specification is provided in the range [fmtBegin, fmtEnd). +// +// By default, formatValue() uses the usual stream insertion operator +// operator<< to format the type T, with special cases for the %c and %p +// conversions. +template +inline void formatValue(std::ostream& out, const char* fmtBegin, + const char* fmtEnd, const T& value) +{ + // The mess here is to support the %c and %p conversions: if these + // conversions are active we try to convert the type to a char or const + // void* respectively and format that instead of the value itself. For the + // %p conversion it's important to avoid dereferencing the pointer, which + // could otherwise lead to a crash when printing a dangling (const char*). + const bool canConvertToChar = detail::is_convertible::value; + const bool canConvertToVoidPtr = detail::is_convertible::value; + if(canConvertToChar && *(fmtEnd-1) == 'c') + detail::formatValueAsType::invoke(out, value); + else if(canConvertToVoidPtr && *(fmtEnd-1) == 'p') + detail::formatValueAsType::invoke(out, value); + else + out << value; +} + + +// Overloaded version for char types to support printing as an integer +#define TINYFORMAT_DEFINE_FORMATVALUE_CHAR(charType) \ +inline void formatValue(std::ostream& out, const char* fmtBegin, \ + const char* fmtEnd, charType value) \ +{ \ + switch(*(fmtEnd-1)) \ + { \ + case 'u': case 'd': case 'i': case 'o': case 'X': case 'x': \ + out << static_cast(value); break; \ + default: \ + out << value; break; \ + } \ +} +// per 3.9.1: char, signed char and unsigned char are all distinct types +TINYFORMAT_DEFINE_FORMATVALUE_CHAR(char) +TINYFORMAT_DEFINE_FORMATVALUE_CHAR(signed char) +TINYFORMAT_DEFINE_FORMATVALUE_CHAR(unsigned char) +#undef TINYFORMAT_DEFINE_FORMATVALUE_CHAR + + +// Format a value into a stream, called for all types by format() +// +// Users should override this function for their own types if they intend to +// completely customize the formatting, and don't want tinyformat to attempt +// to set the stream flags based on the format specifier string. +// +// The format specification is provided in the range [fmtBegin, fmtEnd). +// +// Trying to avoid inlining here greatly reduces bloat in optimized builds +template +TINYFORMAT_NOINLINE +void formatValueBasic(std::ostream& out, const char* fmtBegin, + const char* fmtEnd, const T& value) +{ + // Save stream state + std::streamsize width = out.width(); + std::streamsize precision = out.precision(); + std::ios::fmtflags flags = out.flags(); + char fill = out.fill(); + // Set stream state. + unsigned extraFlags = detail::streamStateFromFormat(out, fmtBegin, fmtEnd); + // Format the value into the stream. + if(!extraFlags) + formatValue(out, fmtBegin, fmtEnd, value); + else + { + // The following are special cases where there's no direct + // correspondence between stream formatting and the printf() behaviour. + // Instead, we simulate the behaviour crudely by formatting into a + // temporary string stream and munging the resulting string. + std::ostringstream tmpStream; + tmpStream.copyfmt(out); + if(extraFlags & detail::Flag_SpacePadPositive) + tmpStream.setf(std::ios::showpos); + // formatCStringTruncate is required for truncating conversions like + // "%.4s" where at most 4 characters of the c-string should be read. + // If we didn't include this special case, we might read off the end. + if(!( (extraFlags & detail::Flag_TruncateToPrecision) && + detail::formatCStringTruncate(tmpStream, value, out.precision()) )) + { + // Not a truncated c-string; just format normally. + formatValue(tmpStream, fmtBegin, fmtEnd, value); + } + std::string result = tmpStream.str(); // allocates... yuck. + if(extraFlags & detail::Flag_SpacePadPositive) + { + for(size_t i = 0, iend = result.size(); i < iend; ++i) + if(result[i] == '+') + result[i] = ' '; + } + if((extraFlags & detail::Flag_TruncateToPrecision) && + (int)result.size() > (int)out.precision()) + out.write(result.c_str(), out.precision()); + else + out << result; + } + // Restore stream state + out.width(width); + out.precision(precision); + out.flags(flags); + out.fill(fill); +} + + +//------------------------------------------------------------------------------ +// Format function, 0-argument case. +inline void format(std::ostream& out, const char* fmt) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + if(*fmt != '\0') + TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string"); +} + + +// Define N-argument format function. +// +// There's two cases here: c++0x and c++98. + +#ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES + +// First, the simple definition for C++0x: + +// N-argument case; formats one element and calls N-1 argument case. +template +void format(std::ostream& out, const char* fmt, const T1& value1, + const Args&... args) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, value1); + format(out, fmtEnd, args...); +} + +#else + +// For C++98 we don't have variadic templates so we need to generate code +// outside the language. We could do this with some ugly macros but instead +// let's use a short snippet of python code with the help of the excellent cog +// code generation script ( http://nedbatchelder.com/code/cog/ ) + +/*[[[cog + +maxParams = 12 + +# prepend a comma if the string isn't empty. +def prependComma(str): + return '' if str == '' else ', ' + str + +# Append backslashes to lines so they appear as a macro in C++ +# lineLen is the desired padding before the backslash +def formatAsMacro(str, lineLen=75): + lines = str.splitlines() + lines = [l+' '*max(1, lineLen-len(l)) for l in lines] + return '\\\n'.join(lines) + '\\' + +# Fill out the given string template. +def fillTemplate(template, minParams=0, formatFunc=lambda s: s): + for i in range(minParams,maxParams+1): + paramRange = range(1,i+1) + templateSpec = ', '.join(['typename T%d' % (j,) for j in paramRange]) + if templateSpec == '': + templateSpec = 'inline' + else: + templateSpec = 'template<%s>' % (templateSpec,) + paramList = prependComma(', '.join(['const T%d& v%d' % (j,j) + for j in paramRange])) + argList = prependComma(', '.join(['v%d' % (j,) for j in paramRange])) + argListNoHead = prependComma(', '.join(['v%d' % (j,) + for j in paramRange[1:]])) + cog.outl(formatFunc(template % locals())) + +fillTemplate( +'''%(templateSpec)s +void format(std::ostream& out, const char* fmt %(paramList)s) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd %(argListNoHead)s); +}''', minParams=1) + +]]]*/ +template +void format(std::ostream& out, const char* fmt , const T1& v1) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd ); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4, v5); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4, v5, v6); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4, v5, v6, v7); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4, v5, v6, v7, v8); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4, v5, v6, v7, v8, v9); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4, v5, v6, v7, v8, v9, v10); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); +} +template +void format(std::ostream& out, const char* fmt , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12) +{ + fmt = detail::printFormatStringLiteral(out, fmt); + const char* fmtEnd = detail::findFormatSpecEnd(fmt); + formatValueBasic(out, fmt, fmtEnd, v1); + format(out, fmtEnd , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); +} +//[[[end]]] + +#endif // End C++98 variadic template emulation for format() + + +//------------------------------------------------------------------------------ +// Define the macro TINYFORMAT_WRAP_FORMAT, which can be used to wrap a call +// to tfm::format for C++98 support. +// +// We make this available in both C++0x and C++98 mode for convenience so that +// users can choose not to write out the C++0x version if they're primarily +// interested in C++98 support, but still have things work with C++0x. +// +// Note that TINYFORMAT_WRAP_EXTRA_ARGS cannot be a macro parameter because it +// must expand to a comma separated list (or nothing, as used for printf below) + +// Define to nothing for convenience. +#define TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS + +/*[[[cog +cog.outl(formatAsMacro( +'''#define TINYFORMAT_WRAP_FORMAT(returnType, funcName, funcDeclSuffix, + bodyPrefix, streamName, bodySuffix)''')) + +fillTemplate( +r'''%(templateSpec)s +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt + %(paramList)s) funcDeclSuffix +{ + bodyPrefix + tinyformat::format(streamName, fmt %(argList)s); + bodySuffix +}''', minParams=0, formatFunc=formatAsMacro) +cog.outl() + +]]]*/ +#define TINYFORMAT_WRAP_FORMAT(returnType, funcName, funcDeclSuffix, \ + bodyPrefix, streamName, bodySuffix) \ +inline \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + ) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt ); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4, v5); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4, v5, v6); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4, v5, v6, v7); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4, v5, v6, v7, v8); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4, v5, v6, v7, v8, v9); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); \ + bodySuffix \ +} \ +template \ +returnType funcName(TINYFORMAT_WRAP_FORMAT_EXTRA_ARGS const char* fmt \ + , const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12) funcDeclSuffix \ +{ \ + bodyPrefix \ + tinyformat::format(streamName, fmt , v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); \ + bodySuffix \ +} \ + +//[[[end]]] + + +//------------------------------------------------------------------------------ +// Implement convenience functions in terms of format(stream, fmt, ...). +// Again, there's two cases. +#ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES + +// C++0x - the simple case +template +std::string format(const char* fmt, const Args&... args) +{ + std::ostringstream oss; + format(oss, fmt, args...); + return oss.str(); +} + +template +void printf(const char* fmt, const Args&... args) +{ + format(std::cout, fmt, args...); +} + +#else + +// C++98 - define the convenience functions using the wrapping macros +// std::string format(const char* fmt, const Args&... args); +TINYFORMAT_WRAP_FORMAT(std::string, format, /*empty*/, + std::ostringstream oss;, oss, + return oss.str();) + +// void printf(const char* fmt, const Args&... args) +TINYFORMAT_WRAP_FORMAT(void, printf, /*empty*/, /*empty*/, std::cout, /*empty*/) + +#endif + +} // namespace tinyformat + +#endif // TINYFORMAT_H_INCLUDED diff --git a/src/include/ustring.h b/src/include/ustring.h index 8ae1ec6d0b..92cd333ee2 100644 --- a/src/include/ustring.h +++ b/src/include/ustring.h @@ -550,7 +550,12 @@ class DLLPUBLIC ustring { /// something like: /// ustring s = ustring::format ("blah %d %g", (int)foo, (float)bar); /// - static ustring format (const char *fmt, ...) OPENIMAGEIO_PRINTF_ARGS(1,2); + /// The printf argument list is fully typesafe via tinyformat; format + /// conceptually has the signature + /// + /// static ustring format (const char *fmt, ...); + TINYFORMAT_WRAP_FORMAT (static ustring, format, /**/, + std::ostringstream msg;, msg, return ustring(msg.str());) /// Generic stream output of a ustring. /// diff --git a/src/libOpenImageIO/imageinput.cpp b/src/libOpenImageIO/imageinput.cpp index 632a2f75aa..f63ab45de1 100644 --- a/src/libOpenImageIO/imageinput.cpp +++ b/src/libOpenImageIO/imageinput.cpp @@ -649,16 +649,13 @@ ImageInput::send_to_client (const char *format, ...) void -ImageInput::error (const char *format, ...) const +ImageInput::append_error (const std::string& message) const { - va_list ap; - va_start (ap, format); ASSERT (m_errmessage.size() < 1024*1024*16 && "Accumulated error messages > 16MB. Try checking return codes!"); if (m_errmessage.size()) m_errmessage += '\n'; - m_errmessage += Strutil::vformat (format, ap); - va_end (ap); + m_errmessage += message; } bool diff --git a/src/libOpenImageIO/imageio.cpp b/src/libOpenImageIO/imageio.cpp index 1d84d89028..f6b025c05b 100644 --- a/src/libOpenImageIO/imageio.cpp +++ b/src/libOpenImageIO/imageio.cpp @@ -61,7 +61,7 @@ namespace { // To avoid thread oddities, we have the storage area buffering error -// messages for error()/geterror() be thread-specific. +// messages for seterror()/geterror() be thread-specific. static thread_specific_ptr thread_error_msg; // Return a reference to the string for this thread's error messages, @@ -94,13 +94,10 @@ openimageio_version () /// Error reporting for the plugin implementation: call this with /// printf-like arguments. void -pvt::error (const char *message, ...) +pvt::seterror (const std::string& message) { recursive_lock_guard lock (pvt::imageio_mutex); - va_list ap; - va_start (ap, message); - error_msg() = Strutil::vformat (message, ap); - va_end (ap); + error_msg() = message; } diff --git a/src/libOpenImageIO/imageio_pvt.h b/src/libOpenImageIO/imageio_pvt.h index 98a8aa728b..e921ba8291 100644 --- a/src/libOpenImageIO/imageio_pvt.h +++ b/src/libOpenImageIO/imageio_pvt.h @@ -57,8 +57,13 @@ extern int oiio_threads; extern ustring plugin_searchpath; -// Use privately only -void error (const char *format, ...) OPENIMAGEIO_PRINTF_ARGS(1,2); +// For internal use - use error() below for a nicer interface. +void seterror (const std::string& message); + +/// Use error() privately only. Protoype is conceptually: +/// void error (const char *format, ...); +TINYFORMAT_WRAP_FORMAT (void, error, /**/, + std::ostringstream msg;, msg, seterror(msg.str());) /// Turn potentially non-contiguous-stride data (e.g. "RGBxRGBx") into /// contiguous-stride ("RGBRGB"), for any format or stride values diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index f3e540807e..a60e6a7316 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -176,16 +176,13 @@ ImageOutput::send_to_client (const char *format, ...) void -ImageOutput::error (const char *format, ...) +ImageOutput::append_error (const std::string& message) const { - va_list ap; - va_start (ap, format); ASSERT (m_errmessage.size() < 1024*1024*16 && "Accumulated error messages > 16MB. Try checking return codes!"); if (m_errmessage.size()) m_errmessage += '\n'; - m_errmessage += Strutil::vformat (format, ap); - va_end (ap); + m_errmessage += message; } diff --git a/src/libtexture/imagecache.cpp b/src/libtexture/imagecache.cpp index a3fd02118d..ec9ce74884 100644 --- a/src/libtexture/imagecache.cpp +++ b/src/libtexture/imagecache.cpp @@ -2466,7 +2466,7 @@ ImageCacheImpl::geterror () const void -ImageCacheImpl::error (const char *message, ...) +ImageCacheImpl::append_error (const std::string& message) const { std::string *errptr = m_errormessage.get (); if (! errptr) { @@ -2478,10 +2478,7 @@ ImageCacheImpl::error (const char *message, ...) "Accumulated error messages > 16MB. Try checking return codes!"); if (errptr->size()) *errptr += '\n'; - va_list ap; - va_start (ap, message); - *errptr += Strutil::vformat (message, ap); - va_end (ap); + *errptr += message; } diff --git a/src/libtexture/imagecache_pvt.h b/src/libtexture/imagecache_pvt.h index 4bb1937632..65dc8bc7b0 100644 --- a/src/libtexture/imagecache_pvt.h +++ b/src/libtexture/imagecache_pvt.h @@ -814,7 +814,12 @@ class ImageCacheImpl : public ImageCache { /// Internal error reporting routine, with printf-like arguments. /// - void error (const char *message, ...) OPENIMAGEIO_PRINTF_ARGS(2,3); + /// void error (const char *message, ...); + TINYFORMAT_WRAP_FORMAT (void, error, const, + std::ostringstream msg;, msg, append_error(msg.str());) + + /// Append a string to the current error message + void append_error (const std::string& message) const; /// Get a pointer to the caller's thread's per-thread info, or create /// one in the first place if there isn't one already. diff --git a/src/libtexture/texture_pvt.h b/src/libtexture/texture_pvt.h index 9e1dc8013e..1a1faaeb5f 100644 --- a/src/libtexture/texture_pvt.h +++ b/src/libtexture/texture_pvt.h @@ -445,7 +445,12 @@ class TextureSystemImpl : public TextureSystem { /// Internal error reporting routine, with printf-like arguments. /// - void error (const char *message, ...) OPENIMAGEIO_PRINTF_ARGS(2,3); + /// void error (const char *message, ...) const + TINYFORMAT_WRAP_FORMAT (void, error, const, + std::ostringstream msg;, msg, append_error(msg.str());) + + /// Append a string to the current error message + void append_error (const std::string& message) const; void printstats () const; diff --git a/src/libtexture/texturesys.cpp b/src/libtexture/texturesys.cpp index ecf4955736..ece51d9302 100644 --- a/src/libtexture/texturesys.cpp +++ b/src/libtexture/texturesys.cpp @@ -539,7 +539,7 @@ TextureSystemImpl::geterror () const void -TextureSystemImpl::error (const char *message, ...) +TextureSystemImpl::append_error (const std::string& message) const { std::string *errptr = m_errormessage.get (); if (! errptr) { @@ -551,10 +551,7 @@ TextureSystemImpl::error (const char *message, ...) "Accumulated error messages > 16MB. Try checking return codes!"); if (errptr->size()) *errptr += '\n'; - va_list ap; - va_start (ap, message); - *errptr += Strutil::vformat (message, ap); - va_end (ap); + *errptr += message; } diff --git a/src/libutil/argparse.cpp b/src/libutil/argparse.cpp index 4b9560000e..2ea1782b41 100644 --- a/src/libutil/argparse.cpp +++ b/src/libutil/argparse.cpp @@ -488,15 +488,6 @@ ArgParse::found (const char *option_name) -void -ArgParse::error (const char *format, ...) -{ - va_list ap; - va_start (ap, format); - m_errmessage = Strutil::vformat (format, ap); - va_end (ap); -} - std::string ArgParse::geterror () const { diff --git a/src/libutil/strutil.cpp b/src/libutil/strutil.cpp index cda7c014bf..2c5284907a 100644 --- a/src/libutil/strutil.cpp +++ b/src/libutil/strutil.cpp @@ -47,18 +47,6 @@ OIIO_NAMESPACE_ENTER { -std::string -Strutil::format (const char *fmt, ...) -{ - va_list ap; - va_start (ap, fmt); - std::string buf = vformat (fmt, ap); - va_end (ap); - return buf; -} - - - std::string Strutil::vformat (const char *fmt, va_list ap) { diff --git a/src/libutil/ustring.cpp b/src/libutil/ustring.cpp index ececd21881..ee5629d67c 100644 --- a/src/libutil/ustring.cpp +++ b/src/libutil/ustring.cpp @@ -238,44 +238,6 @@ ustring::make_unique (const char *str) -ustring -ustring::format (const char *fmt, ...) -{ - // Allocate a buffer on the stack that's big enough for us almost - // all the time. Be prepared to allocate dynamically if it doesn't fit. - size_t size = 1024; - char stackbuf[1024]; - std::vector dynamicbuf; - char *buf = &stackbuf[0]; - - while (1) { - // Try to vsnprintf into our buffer. - va_list ap; - va_start (ap, fmt); - int needed = vsnprintf (buf, size, fmt, ap); - va_end (ap); - - // NB. C99 (which modern Linux and OS X follow) says vsnprintf - // failure returns the length it would have needed. But older - // glibc and current Windows return -1 for failure, i.e., not - // telling us how much was needed. - - if (needed < (int)size && needed >= 0) { - // It fit fine so we're done. - return ustring (buf); - } - - // vsnprintf reported that it wanted to write more characters - // than we allotted. So try again using a dynamic buffer. This - // doesn't happen very often if we chose our initial size well. - size = (needed > 0) ? (needed+1) : (size*2); - dynamicbuf.resize (size); - buf = &dynamicbuf[0]; - } -} - - - std::string ustring::getstats (bool verbose) {