[glibmm/glibmm-2-62] ustring: Add sprintf(), wrapping g_strdup_printf()



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]