[glibmm/glibmm-2-62] ustring: Add sprintf(), wrapping g_strdup_printf()
- From: Kjell Ahlstedt <kjellahl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glibmm/glibmm-2-62] ustring: Add sprintf(), wrapping g_strdup_printf()
- Date: Sun, 15 Sep 2019 17:22:48 +0000 (UTC)
commit 3705b620d4f0a3492fd66b5a68638a1a8f0a9b8b
Author: Daniel Boles <dboles src gnome org>
Date: Sun Sep 15 19:18:48 2019 +0200
ustring: Add sprintf(), wrapping g_strdup_printf()
This is a manually squashed version of several commits in the master branch.
Squashed by Kjell Ahlstedt.
Fixes #21
glib/glibmm/ustring.h | 158 +++++++++++++++++++++++++++++++++++
tests/Makefile.am | 2 +
tests/glibmm_ustring_sprintf/main.cc | 51 +++++++++++
3 files changed, 211 insertions(+)
---
diff --git a/glib/glibmm/ustring.h b/glib/glibmm/ustring.h
index 32700758..f0c4d869 100644
--- a/glib/glibmm/ustring.h
+++ b/glib/glibmm/ustring.h
@@ -812,6 +812,99 @@ public:
template <class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
static inline ustring format(const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5,
const T6& a6, const T7& a7, const T8& a8);
+
+ /*! Substitute placeholders in a format string with the referenced arguments.
+ *
+ * This function takes a template string in the format used by C’s
+ * <tt>printf()</tt> family of functions and an arbitrary number of arguments,
+ * replaces each placeholder in the template with the formatted version of its
+ * corresponding argument at the same ordinal position in the list of
+ * subsequent arguments, and returns the result in a new Glib::ustring.
+ *
+ * Note: You must pass the correct count/types/order of arguments to match
+ * the format string, as when calling <tt>printf()</tt> directly. glibmm does
+ * not check this for you. Breaking this contract invokes undefined behavior
+ * and is a security risk.
+ *
+ * The exception is that glibmm special-cases std::string and Glib::ustring,
+ * so you can pass them in positions corresponding to <tt>%s</tt> placeholders
+ * without having to call their .c_str() functions; glibmm does that for you.
+ * glibmm also overloads sprintf() with @p fmt but no @p args to avoid risks.
+ *
+ * Said restriction also makes sprintf() unsuitable for translatable strings,
+ * as translators cannot reorder the placeholders to suit their language. If
+ * you wish to support translation, you should instead use compose(), as its
+ * placeholders are numbered rather than ordinal, so they can be moved freely.
+ *
+ * @par Example:
+ * @code
+ *
+ * const auto greeting = std::string{"Hi"};
+ * const auto name = Glib::ustring{"Dennis"};
+ * const auto your_cows = 3;
+ * const auto my_cows = 11;
+ * const auto cow_percentage = 100.0 * your_cows / my_cows;
+ *
+ * const auto text = Glib::ustring::sprintf(
+ * "%s, %s! You have %d cows. That's about %0.2f%% of the %d cows I have.",
+ * greeting, name, your_cows, cow_percentage, my_cows);
+ *
+ * std::cout << text;
+ * // Hi, Dennis! You have 3 cows. That's about 27.27% of the 11 cows I have.
+ * @endcode
+ *
+ * @param fmt The template string, in the format used by <tt>printf()</tt> et al.
+ * @param args A set of arguments having the count/types/order required by @a fmt.
+ *
+ * @return The substituted string.
+ *
+ * @newin{2,62}
+ */
+ template <class... Ts>
+ static inline ustring sprintf(const ustring& fmt, const Ts&... args);
+
+ /*! Overload of sprintf() taking a string literal.
+ *
+ * The main benefit of this is not constructing a temporary ustring if @p fmt
+ * is a string literal. A secondary effect is that it might encourage compilers
+ * to check if the given format @p fmt matches the variadic arguments @p args.
+ * The latter effect is a convenience at best; you must not rely on it to find
+ * errors in your code, as your compiler might not always be able to do so.
+ *
+ * @param fmt The template string, in the format used by <tt>printf()</tt> et al.
+ * @param args A set of arguments having the count/types/order required by @a fmt.
+ *
+ * @return The substituted string.
+ *
+ * @newin{2,62}
+ */
+ template <class... Ts>
+ static inline ustring sprintf(const char* fmt, const Ts&... args);
+
+ /*! Overload of sprintf() for a format string only, which returns it unchanged.
+ *
+ * If no @p args to be substituted are given, there is nothing to do, so the
+ * @p fmt string is returned as-is without substitution. This is an obvious
+ * case of mismatched format/args that we can check. Not doing so causes
+ * warnings/errors with common compiler options, as it is a security risk.
+ *
+ * @param fmt The string
+ * @return The same string.
+ *
+ * @newin{2,62}
+ */
+ static inline ustring sprintf(const ustring& fmt);
+
+ /*! Overload of sprintf() for a format string only, which returns it unchanged
+ * and avoids creating a temporary ustring as the argument.
+ *
+ * @param fmt The string
+ * @return The same string, as a ustring.
+ *
+ * @newin{2,62}
+ */
+ static inline ustring sprintf(const char* fmt);
+
//! @}
private:
@@ -837,6 +930,10 @@ private:
static ustring compose_argv(const ustring& fmt, int argc, const ustring* const* argv);
+ template<class T> static inline const T& sprintify(const T& arg);
+ static inline const char* sprintify(const ustring& arg);
+ static inline const char* sprintify(const std::string& arg);
+
#endif /* DOXYGEN_SHOULD_SKIP_THIS */
std::string string_;
@@ -1365,6 +1462,33 @@ inline // static
return ustring::compose_argv(fmt, 0, nullptr);
}
+/* These helper functions used by ustring::sprintf() let users pass C++ strings
+ * to match %s placeholders, without the hassle of writing .c_str() in user code
+ */
+template<typename T>
+inline // static
+ const T&
+ ustring::sprintify(const T& arg)
+{
+ return arg;
+}
+
+inline // static
+ const char*
+ ustring::sprintify(const ustring& arg)
+{
+ return arg.c_str();
+}
+
+inline // static
+ const char*
+ ustring::sprintify(const std::string& arg)
+{
+ return arg.c_str();
+}
+
+// Public methods
+
template <class T1>
inline // static
ustring
@@ -1508,6 +1632,40 @@ inline // static
return ustring::compose_argv(fmt, G_N_ELEMENTS(argv), argv);
}
+template <class... Ts>
+inline // static
+ ustring
+ ustring::sprintf(const ustring& fmt, const Ts&... args)
+{
+ return sprintf(fmt.c_str(), args...);
+}
+
+template <class... Ts>
+inline // static
+ ustring
+ ustring::sprintf(const char* fmt, const Ts&... args)
+{
+ auto c_str = g_strdup_printf(fmt, sprintify(args)...);
+ Glib::ustring ustr(c_str);
+ g_free(c_str);
+
+ return ustr;
+}
+
+inline // static
+ ustring
+ ustring::sprintf(const ustring& fmt)
+{
+ return fmt;
+}
+
+inline // static
+ ustring
+ ustring::sprintf(const char* fmt)
+{
+ return ustring(fmt);
+}
+
#endif /* DOXYGEN_SHOULD_SKIP_THIS */
/** @relates Glib::ustring */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2cb35abb..b7336354 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -37,6 +37,7 @@ check_PROGRAMS = \
glibmm_objectbase_move/test \
glibmm_ustring_compose/test \
glibmm_ustring_format/test \
+ glibmm_ustring_sprintf/test \
glibmm_value/test \
glibmm_valuearray/test \
glibmm_variant/test \
@@ -102,6 +103,7 @@ glibmm_object_move_test_SOURCES = glibmm_object_move/main.cc
glibmm_objectbase_move_test_SOURCES = glibmm_objectbase_move/main.cc
glibmm_ustring_compose_test_SOURCES = glibmm_ustring_compose/main.cc
glibmm_ustring_format_test_SOURCES = glibmm_ustring_format/main.cc
+glibmm_ustring_sprintf_test_SOURCES = glibmm_ustring_sprintf/main.cc
glibmm_value_test_SOURCES = glibmm_value/glibmm_value.cc glibmm_value/main.cc
glibmm_valuearray_test_SOURCES = glibmm_valuearray/main.cc
glibmm_variant_test_SOURCES = glibmm_variant/main.cc
diff --git a/tests/glibmm_ustring_sprintf/main.cc b/tests/glibmm_ustring_sprintf/main.cc
new file mode 100644
index 00000000..cfa27554
--- /dev/null
+++ b/tests/glibmm_ustring_sprintf/main.cc
@@ -0,0 +1,51 @@
+#include <glibmm/init.h>
+#include <glibmm/ustring.h>
+
+#include <cstdlib>
+#include <iostream>
+
+namespace {
+
+template <class... Ts>
+void
+test(const Glib::ustring& expected, const Glib::ustring& fmt, const Ts&... ts)
+{
+ const auto actual = Glib::ustring::sprintf(fmt, ts...);
+
+ if (actual != expected)
+ {
+ std::cerr << "error testing Glib::ustring::sprintf():\n"
+ "expected (" << expected.size() << "):\n" << expected << "\n\n"
+ "actual (" << actual .size() << "):\n" << actual << "\n";
+
+ std::exit(EXIT_FAILURE);
+ }
+}
+
+} // anonymous namespace
+
+int
+main(int, char**)
+{
+ Glib::init();
+
+ test("No formatting here, just a boring string",
+ "No formatting here, just a boring string");
+
+ test("Interpolating another string: \"here it is\" and there it was gone.",
+ "Interpolating another string: \"%s\" and there it was gone.", "here it is");
+
+ test("some stuff and then an int: 42",
+ "some stuff and then an int: %d", 42);
+
+ const auto greeting = std::string{"Hi"};
+ const auto name = Glib::ustring{"Dennis"};
+ const auto your_cows = 3;
+ const auto my_cows = 11;
+ const auto cow_percentage = 100.0 * your_cows / my_cows;
+ test("Hi, Dennis! You have 3 cows.\nThat's about 27.27% of the 11 cows I have.",
+ "%s, %s! You have %d cows.\nThat's about %0.2f%% of the %d cows I have.",
+ greeting, name, your_cows, cow_percentage, my_cows);
+
+ return EXIT_SUCCESS;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]