[glibmm/wip/dboles/ustring-sprintf: 47/47] ustring: Add sprintf(), wrapping g_strdup_printf()



commit 4a67b2e9c75e0dd684e022d3deff19cf3d9721f1
Author: Daniel Boles <dboles src gnome org>
Date:   Mon Dec 17 01:14:45 2018 +0000

    ustring: Add sprintf(), wrapping g_strdup_printf()
    
    Add another way to produce formatted ustrings, this time using printf
    syntax, by forwarding arguments to g_strdup_printf() and then copying
    the result into the returned ustring.
    
    This includes a private ustring::sprintify() function that by default
    just forward its argument but can be overloaded to do something else.
    In this commit, that is overloaded for ustring and std::string so that
    their .c_str() is passed to printf instead, avoiding the ugliness of
    users always having to write .c_str() in their own lists of arguments.
    
    Note that the same lack of type safety as plagues printf() and all its
    variants (in both C and GLib) applies here: the arguments are just
    forwarded on, so if you include too few or the wrong types for the
    placeholders you specify, you invoke undefined behaviour just as in C.
    
    https://gitlab.gnome.org/GNOME/glibmm/issues/21

 glib/glibmm/ustring.h                | 93 ++++++++++++++++++++++++++++++++++--
 tests/Makefile.am                    |  2 +
 tests/glibmm_ustring_sprintf/main.cc | 55 +++++++++++++++++++++
 3 files changed, 147 insertions(+), 3 deletions(-)
---
diff --git a/glib/glibmm/ustring.h b/glib/glibmm/ustring.h
index edd713db..75f33775 100644
--- a/glib/glibmm/ustring.h
+++ b/glib/glibmm/ustring.h
@@ -192,9 +192,11 @@ gunichar get_unichar_from_std_iterator(std::string::const_iterator pos) G_GNUC_P
  *
  * @par Formatted output and internationalization
  * @par
- * The methods ustring::compose() and ustring::format() provide a convenient
- * and powerful alternative to string streams, as shown in the example below.
- * Refer to the method documentation of compose() and format() for details.
+ * The methods ustring::compose(), ustring::format(), and
+ * ustring::sprintf() provide a convenient and powerful alternative to
+ * string streams, as shown in the example below. Refer to those methods’
+ * documentation for details.
+ *
  * @code
  * using Glib::ustring;
  *
@@ -714,6 +716,48 @@ public:
   template <class... Ts>
   static inline ustring format(const Ts&... args);
 
+  /*! 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, and returns the result in a new Glib::ustring.
+   *
+   * Note that you must pass the correct number and types 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.
+   *
+   * 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.
+   *
+   * @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 2 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 number and types required by @a fmt.
+   *
+   * @return The substituted message string.
+   *
+   * @newin{2,56}
+   */
+  template <class... Ts>
+  static inline ustring sprintf(const ustring& fmt, const Ts&... args);
+
   //! @}
 
 private:
@@ -740,6 +784,10 @@ private:
 
   class FormatStream;
 
+  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_;
@@ -1156,6 +1204,33 @@ public:
   inline const ustring& ref() const { return string_; }
 };
 
+/* 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
+
 inline // static
   ustring
   ustring::compose(const ustring& fmt)
@@ -1174,6 +1249,18 @@ inline // static
   return compose_private(fmt, {&Stringify<Ts>(args).ref()...});
 }
 
+template <class... Ts>
+inline // static
+  ustring
+  ustring::sprintf(const ustring& fmt, const Ts&... args)
+{
+  auto c_str = g_strdup_printf(fmt.c_str(), sprintify(args)...);
+  Glib::ustring ustr(c_str);
+  g_free(c_str);
+
+  return ustr;
+}
+
 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
 
 /** @relates Glib::ustring */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 339eeada..3f6f81c9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -40,6 +40,7 @@ check_PROGRAMS =                              \
        glibmm_objectbase_move/test                     \
        glibmm_ustring_compose/test             \
        glibmm_ustring_format/test              \
+       glibmm_ustring_sprintf/test             \
        glibmm_value/test                       \
        glibmm_variant/test                     \
        glibmm_vector/test                      \
@@ -113,6 +114,7 @@ glibmm_objectbase_move_test_SOURCES      = glibmm_objectbase_move/main.cc \
                                           glibmm_object/test_derived_object.h
 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/main.cc
 glibmm_variant_test_SOURCES              = glibmm_variant/main.cc
 glibmm_vector_test_SOURCES               = glibmm_vector/main.cc
diff --git a/tests/glibmm_ustring_sprintf/main.cc b/tests/glibmm_ustring_sprintf/main.cc
new file mode 100644
index 00000000..232be152
--- /dev/null
+++ b/tests/glibmm_ustring_sprintf/main.cc
@@ -0,0 +1,55 @@
+#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**)
+{
+  // Don't use the user's preferred locale. The decimal delimiter may be ','
+  // instead of the expected '.'.
+  Glib::set_init_to_users_preferred_locale(false);
+
+  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]