diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2021-11-23 09:58:54 -0500 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2021-11-23 09:58:54 -0500 |
commit | a32a45163d18f9b5998e469a356f870dbdb034ad (patch) | |
tree | b800e7fe9da36ec3c1d4d06666a20aadf5b4b40f /indra/llcommon | |
parent | 53cf740d874f376f212a5d706ca70cbf35fee259 (diff) |
SL-16094: Extend stringize() to support variadic arguments.
It's useful to be able to say STRINGIZE(item0 << item1 << item2), and we use
that a lot in our code base. But weird syntax aside, there are a couple
advantages to being able to write stringize(item0, item1, item2).
First, it allows stringize() to be used from within some other variadic
function, without having to make that function a macro that accepts an
arbitrary insertion-operator expression. There's no such thing as a member
macro.
Second, particularly for variadic functions, it allows us to optimize the
single-argument case stringize(item0). A macro can't do that. When item0 is
already a string of the desired char type, instead of streaming it into a
std::ostringstream and retrieving it again, we can simply return the input
string. When it's a pointer to the desired char type, we can directly
construct the result string without the help of std::ostringstream. When it's
a string of some other char type, we can engage ll_convert() to perform needed
conversions.
We generalize and optimize the generic gstringize() function, retaining the
role of stringize() and wstringize() as thin wrappers that merely provide the
desired char type.
Optimizing the single-argument case requires separately defining gstringize()
with two or more arguments: the general case. Then gstringize(arg) is
delegated to a gstringize_impl class template so we can partially specialize
to recognize a std::basic_string<desired_char_type> argument, as well as
desired_char_type*. Both these specializations engage ll_convert(), which
already handles the trivial case when no conversion is required.
Use of ll_convert() in this role supercedes and generalizes the previous
wstring_to_utf8str() and utf8str_to_wstring() overloads.
Also introduce stream_to(std::ostream&, ...) to support variadic streaming to
other destinations, e.g. a file, std::cout, ...
Diffstat (limited to 'indra/llcommon')
-rw-r--r-- | indra/llcommon/stringize.h | 109 |
1 files changed, 80 insertions, 29 deletions
diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index 38dd198ad3..bc91f188e4 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -31,58 +31,109 @@ #include <sstream> #include <llstring.h> +#include <boost/call_traits.hpp> /** - * gstringize(item) encapsulates an idiom we use constantly, using - * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str() - * or their wstring equivalents - * to render a string expressing some item. + * stream_to(std::ostream&, items, ...) streams each item in the parameter list + * to the passed std::ostream using the insertion operator <<. This can be + * used, for instance, to make a simple print() function, e.g.: + * + * @code + * template <typename... Items> + * void print(Items&&... items) + * { + * stream_to(std::cout, std::forward<Items>(items)...); + * } + * @endcode */ -template <typename CHARTYPE, typename T> -std::basic_string<CHARTYPE> gstringize(const T& item) +// recursion tail +template <typename CHARTYPE> +void stream_to(std::basic_ostream<CHARTYPE>& out) {} +// stream one or more items +template <typename CHARTYPE, typename T, typename... Items> +void stream_to(std::basic_ostream<CHARTYPE>& out, T&& item, Items&&... items) { - std::basic_ostringstream<CHARTYPE> out; - out << item; - return out.str(); + out << std::move(item); + stream_to(out, std::forward<Items>(items)...); } +// why we use function overloads, not function template specializations: +// http://www.gotw.ca/publications/mill17.htm + /** - *partial specialization of stringize for handling wstring - *TODO: we should have similar specializations for wchar_t[] but not until it is needed. + * gstringize(item, ...) encapsulates an idiom we use constantly, using + * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str() + * or their wstring equivalents to render a string expressing one or more items. */ -inline std::string stringize(const std::wstring& item) +// two or more args - the case of a single argument is handled separately +template <typename CHARTYPE, typename T0, typename T1, typename... Items> +auto gstringize(T0&& item0, T1&& item1, Items&&... items) { - return wstring_to_utf8str(item); + std::basic_ostringstream<CHARTYPE> out; + stream_to(out, std::forward<T0>(item0), std::forward<T1>(item1), + std::forward<Items>(items)...); + return out.str(); } -/** - * Specialization of gstringize for std::string return types - */ -template <typename T> -std::string stringize(const T& item) +// generic single argument: stream to out, as above +template <typename CHARTYPE, typename T> +struct gstringize_impl { - return gstringize<char>(item); + auto operator()(typename boost::call_traits<T>::param_type arg) + { + std::basic_ostringstream<CHARTYPE> out; + out << arg; + return out.str(); + } +}; + +// partially specialize for a single STRING argument - +// note that ll_convert<T>(T) already handles the trivial case +template <typename OUTCHAR, typename INCHAR> +struct gstringize_impl<OUTCHAR, std::basic_string<INCHAR>> +{ + auto operator()(const std::basic_string<INCHAR>& arg) + { + return ll_convert<std::basic_string<OUTCHAR>>(arg); + } +}; + +// partially specialize for a single CHARTYPE* argument - +// since it's not a basic_string and we do want to optimize this common case +template <typename OUTCHAR, typename INCHAR> +struct gstringize_impl<OUTCHAR, INCHAR*> +{ + auto operator()(const INCHAR* arg) + { + return ll_convert<std::basic_string<OUTCHAR>>(arg); + } +}; + +// gstringize(single argument) +template <typename CHARTYPE, typename T> +auto gstringize(T&& item) +{ + // use decay<T> so we don't require separate specializations for T, const + // T, T&, const T& ... + return gstringize_impl<CHARTYPE, std::decay_t<T>>()(std::forward<T>(item)); } /** - * Specialization for generating wstring from string. - * Both a convenience function and saves a miniscule amount of overhead. + * Specialization of gstringize for std::string return types */ -inline std::wstring wstringize(const std::string& item) +template <typename... Items> +auto stringize(Items&&... items) { - // utf8str_to_wstring() returns LLWString, which isn't necessarily the - // same as std::wstring - LLWString s(utf8str_to_wstring(item)); - return std::wstring(s.begin(), s.end()); + return gstringize<char>(std::forward<Items>(items)...); } /** * Specialization of gstringize for std::wstring return types */ -template <typename T> -std::wstring wstringize(const T& item) +template <typename... Items> +auto wstringize(Items&&... items) { - return gstringize<wchar_t>(item); + return gstringize<wchar_t>(std::forward<Items>(items)...); } /** |