summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--glib/glibmm/ustring.h91
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/glibmm_ustring_sprintf/main.cc55
3 files changed, 148 insertions, 0 deletions
diff --git a/glib/glibmm/ustring.h b/glib/glibmm/ustring.h
index edd713db..0c1fa015 100644
--- a/glib/glibmm/ustring.h
+++ b/glib/glibmm/ustring.h
@@ -714,6 +714,54 @@ 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 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 number/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.
+ *
+ * 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.
+ *
+ * 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 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,62}
+ */
+ template <class... Ts>
+ static inline ustring sprintf(const ustring& fmt, const Ts&... args);
+
//! @}
private:
@@ -740,6 +788,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 +1208,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 +1253,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;
+}