diff options
-rw-r--r-- | glib/glibmm/ustring.h | 91 | ||||
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/glibmm_ustring_sprintf/main.cc | 55 |
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; +} |