diff --git a/CMakeLists.txt b/CMakeLists.txt index b08f9cbe..d99babdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,14 @@ message(STATUS "CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12) +if (POLICY CMP0048) # Version variables + cmake_policy(SET CMP0048 OLD) +endif () + +if (POLICY CMP0063) # Visibility + cmake_policy(SET CMP0063 OLD) +endif (POLICY CMP0063) + # Determine if fmt is built as a subproject (using add_subdirectory) # or if it is the master project. set(MASTER_PROJECT OFF) diff --git a/ChangeLog.rst b/ChangeLog.rst index 4a16be95..ce0406a9 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,7 +1,32 @@ +4.1.0 - 2017-12-20 +------------------ + +* Added ``fmt::to_wstring()`` in addition to ``fmt::to_string()`` (`#559 `_). Thanks `@alabuzhev (Alex Alabuzhev) `_. + +* Added support for C++17 ``std::string_view`` (`#571 `_ and `#578 `_). Thanks `@thelostt (Mário Feroldi) `_ and `@mwinterb `_. + +* Enabled stream exceptions to catch errors (`#581 `_). Thanks `@crusader-mike `_. + +* Allowed formatting of class hierarchies with ``fmt::format_arg()`` (`#547 `_). Thanks `@rollbear (Björn Fahller) `_. + +* Removed limitations on character types + (`#563 `_). + Thanks `@Yelnats321 (Elnar Dakeshov) `_. + +* Conditionally enabled use of ``std::allocator_traits`` (`#583 `_). Thanks `@mwinterb `_. + +* Added support for ``const`` variadic member function emulation with ``FMT_VARIADIC_CONST`` (`#591 `_). Thanks `@ludekvodicka (Ludek Vodicka) `_. + +* Various bugfixes: bad overflow check, unsupported implicit type conversion when determining formatting function, test segfaults (`#551 `_), ill-formed macros (`#542 `_) and ambiguous overloads (`#580 `_). Thanks `@xylosper (Byoung-young Lee) `_. + +* Prevented warnings on MSVC (`#605 `_, `#602 `_, and `#545 `_), clang (`#582 `_), GCC (`#573 `_), various conversion warnings (`#609 `_, `#567 `_, `#553 `_ and `#553 `_), and added ``override`` and ``[[noreturn]]`` (`#549 `_ and `#555 `_). Thanks `@alabuzhev (Alex Alabuzhev) `_, `@virgiliofornazin (Virgilio Alexandre Fornazin) `_, `@alexanderbock (Alexander Bock) `_, `@yumetodo `_, `@VaderY (Császár Mátyás) `_, `@jpcima (JP Cimalando) `_, `@thelostt (Mário Feroldi) `_, and `@Manu343726 (Manu Sánchez) `_. + +* Improved CMake: Used GNUInstallDirs to set installation location (`#610 `_) and fixed warnings (`#536 `_ and `#556 `_). Thanks `@mikecrowe (Mike Crowe) `_, `@evgen231 `_ and `@henryiii (Henry Schreiner) `_. + 4.0.0 - 2017-06-27 ------------------ -* Removed old compatibility headers ``cppformat/*.h`` and CMake options (`#527 `_). Thanks `@maddinat0r (Alex Martin) `_. +* Removed old compatibility headers ``cppformat/*.h`` and CMake options (`#527 `_). Thanks `@maddinat0r (Alex Martin) `_. * Added ``string.h`` containing ``fmt::to_string()`` as alternative to ``std::to_string()`` as well as other string writer functionality (`#326 `_ and `#441 `_): diff --git a/README.rst b/README.rst index ea2a6da4..32f50257 100644 --- a/README.rst +++ b/README.rst @@ -142,6 +142,8 @@ Projects using this library * `Envoy `_: C++ L7 proxy and communication bus (Lyft) +* `FiveM `_: a modification framework for GTA V + * `HarpyWar/pvpgn `_: Player vs Player Gaming Network with tweaks @@ -155,6 +157,8 @@ Projects using this library * `MongoDB Smasher `_: A small tool to generate randomized datasets +* `OpenSpace `_: An open-source astrovisualization framework + * `PenUltima Online (POL) `_: An MMO server, compatible with most Ultima Online clients diff --git a/doc/api.rst b/doc/api.rst index 8dbef39e..9647c90b 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -84,6 +84,36 @@ Note in the example above the ``format_arg`` function ignores the contents of ``format_arg`` in :file:`fmt/time.h` for an advanced example of how to use the ``format_str`` argument to customize the formatted output. +This technique can also be used for formatting class hierarchies:: + + namespace local { + struct Parent { + Parent(int p) : p(p) {} + virtual void write(fmt::Writer &w) const { + w.write("Parent : p={}", p); + } + int p; + }; + + struct Child : Parent { + Child(int c, int p) : Parent(p), c(c) {} + virtual void write(fmt::Writer &w) const { + w.write("Child c={} : ", c); + Parent::write(w); + } + int c; + }; + + void format_arg(fmt::BasicFormatter &f, + const char *&format_str, const Parent &p) { + p.write(f.writer()); + } + } + Local::Child c(1,2); + Local::Parent &p = c; + fmt::print("via ref to base: {}\n", p); + fmt::print("direct to child: {}\n", c); + This section shows how to define a custom format function for a user-defined type. The next section describes how to get ``fmt`` to use a conventional stream output ``operator<<`` when one is defined for a user-defined type. @@ -232,6 +262,8 @@ Utilities .. doxygenfunction:: fmt::to_string(const T&) +.. doxygenfunction:: fmt::to_wstring(const T&) + .. doxygenclass:: fmt::BasicStringRef :members: diff --git a/doc/build.py b/doc/build.py index 55992b9f..cb8d0c3c 100755 --- a/doc/build.py +++ b/doc/build.py @@ -6,6 +6,8 @@ import errno, os, shutil, sys, tempfile from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE from distutils.version import LooseVersion +versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0'] + def pip_install(package, commit=None, **kwargs): "Install package using pip." min_version = kwargs.get('min_version') @@ -93,11 +95,11 @@ def build_docs(version='dev', **kwargs): if p.returncode != 0: raise CalledProcessError(p.returncode, cmd) html_dir = os.path.join(work_dir, 'html') - versions = ['3.0.0', '2.0.0', '1.1.0'] + main_versions = reversed(versions[-3:]) check_call(['sphinx-build', '-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir), '-Dversion=' + version, '-Drelease=' + version, - '-Aversion=' + version, '-Aversions=' + ','.join(versions), + '-Aversion=' + version, '-Aversions=' + ','.join(main_versions), '-b', 'html', doc_dir, html_dir]) try: check_call(['lessc', '--clean-css', diff --git a/doc/index.rst b/doc/index.rst index ce9b7bf9..a00828b0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -143,6 +143,12 @@ its numeric value being written to the stream (i.e. 1070 instead of letter 'ю' which is represented by ``L'\x42e'`` if we use Unicode) which is rarely what is needed. +Note that fmt does not use the value of the ``errno`` global to communicate +errors to the user, but it may call system functions which set ``errno``. Since +fmt does not attempt to preserve the value of ``errno``, users should not make +any assumptions about it and always set it to ``0`` before making any system +calls that convey error information via ``errno``. + .. _portability: Portability diff --git a/doc/syntax.rst b/doc/syntax.rst index 1051467a..e6d2efeb 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -335,6 +335,16 @@ Aligning the text and specifying a width:: format("{:*^30}", "centered"); // use '*' as a fill char // Result: "***********centered***********" +Dynamic width:: + + format("{:<{}}", "left aligned", 30); + // Result: "left aligned " + +Dynamic precision:: + + format("{:.{}f}", 3.14, 1); + // Result: "3.1" + Replacing ``%+f``, ``%-f``, and ``% f`` and specifying a sign:: format("{:+f}; {:+f}", 3.14, -3.14); // show it always @@ -350,7 +360,7 @@ Replacing ``%x`` and ``%o`` and converting the value to different bases:: // Result: "int: 42; hex: 2a; oct: 52; bin: 101010" // with 0x or 0 or 0b as prefix: format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42); - // Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010" + // Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010" .. ifconfig:: False @@ -359,13 +369,6 @@ Replacing ``%x`` and ``%o`` and converting the value to different bases:: format("{:,}", 1234567890); '1,234,567,890' - Expressing a percentage:: - - >>> points = 19 - >>> total = 22 - Format("Correct answers: {:.2%}") << points/total) - 'Correct answers: 86.36%' - Using type-specific formatting:: >>> import datetime diff --git a/doc/usage.rst b/doc/usage.rst index dff312df..4c65f8ab 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -41,6 +41,10 @@ current directory. Now you can build the library by running :command:`make`. Once the library has been built you can invoke :command:`make test` to run the tests. +You can control generation of the make ``test`` target with the ``FMT_TEST`` +CMake option. This can be useful if you include fmt as a subdirectory in +your project but don't want to add fmt's tests to your ``test`` target. + If you use Windows and have Visual Studio installed, a :file:`FORMAT.sln` file and several :file:`.vcproj` files will be created. You can then build them using Visual Studio or msbuild. diff --git a/fmt/CMakeLists.txt b/fmt/CMakeLists.txt index 90eaf575..3259d788 100644 --- a/fmt/CMakeLists.txt +++ b/fmt/CMakeLists.txt @@ -49,8 +49,9 @@ endif () # Install targets. if (FMT_INSTALL) + include(GNUInstallDirs) include(CMakePackageConfigHelpers) - set(FMT_CMAKE_DIR lib/cmake/fmt CACHE STRING + set(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) @@ -61,9 +62,12 @@ if (FMT_INSTALL) set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only) endif () - set(FMT_LIB_DIR lib CACHE STRING + set(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING "Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.") + set(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING + "Installation directory for include files, relative to ${CMAKE_INSTALL_PREFIX}.") + # Generate the version, config and target files into the build directory. write_basic_package_version_file( ${version_config} @@ -86,5 +90,5 @@ if (FMT_INSTALL) # Install the library and headers. install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} DESTINATION ${FMT_LIB_DIR}) - install(FILES ${FMT_HEADERS} DESTINATION include/fmt) + install(FILES ${FMT_HEADERS} DESTINATION ${FMT_INC_DIR}) endif () diff --git a/fmt/format.cc b/fmt/format.cc index 09d2ea9f..2d236bc6 100644 --- a/fmt/format.cc +++ b/fmt/format.cc @@ -72,9 +72,11 @@ // Dummy implementations of strerror_r and strerror_s called if corresponding // system functions are not available. +FMT_MAYBE_UNUSED static inline fmt::internal::Null<> strerror_r(int, char *, ...) { return fmt::internal::Null<>(); } +FMT_MAYBE_UNUSED static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { return fmt::internal::Null<>(); } @@ -121,7 +123,7 @@ typedef void (*FormatFunc)(Writer &, int, StringRef); // Buffer should be at least of size 1. int safe_strerror( int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { - FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer"); + FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); class StrError { private: @@ -159,6 +161,11 @@ int safe_strerror( ERANGE : result; } +#ifdef __c2__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + // Fallback to strerror if strerror_r and strerror_s are not available. int fallback(internal::Null<>) { errno = 0; @@ -166,13 +173,15 @@ int safe_strerror( return errno; } +#ifdef __c2__ +# pragma clang diagnostic pop +#endif + public: StrError(int err_code, char *&buf, std::size_t buf_size) : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} int run() { - // Suppress a warning about unused strerror_r. - strerror_r(0, FMT_NULL, ""); return handle(strerror_r(error_code_, buffer_, buffer_size_)); } }; @@ -396,51 +405,6 @@ FMT_FUNC void format_system_error( fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. } -template -void internal::ArgMap::init(const ArgList &args) { - if (!map_.empty()) - return; - typedef internal::NamedArg NamedArg; - const NamedArg *named_arg = FMT_NULL; - bool use_values = - args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE; - if (use_values) { - for (unsigned i = 0;/*nothing*/; ++i) { - internal::Arg::Type arg_type = args.type(i); - switch (arg_type) { - case internal::Arg::NONE: - return; - case internal::Arg::NAMED_ARG: - named_arg = static_cast(args.values_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); - break; - default: - /*nothing*/; - } - } - return; - } - for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) { - internal::Arg::Type arg_type = args.type(i); - if (arg_type == internal::Arg::NAMED_ARG) { - named_arg = static_cast(args.args_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); - } - } - for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) { - switch (args.args_[i].type) { - case internal::Arg::NONE: - return; - case internal::Arg::NAMED_ARG: - named_arg = static_cast(args.args_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); - break; - default: - /*nothing*/; - } - } -} - template void internal::FixedBuffer::grow(std::size_t) { FMT_THROW(std::runtime_error("buffer overflow")); @@ -502,8 +466,6 @@ template struct internal::BasicData; template void internal::FixedBuffer::grow(std::size_t); -template void internal::ArgMap::init(const ArgList &args); - template FMT_API int internal::CharTraits::format_float( char *buffer, std::size_t size, const char *format, unsigned width, int precision, double value); @@ -516,8 +478,6 @@ template FMT_API int internal::CharTraits::format_float( template void internal::FixedBuffer::grow(std::size_t); -template void internal::ArgMap::init(const ArgList &args); - template FMT_API int internal::CharTraits::format_float( wchar_t *buffer, std::size_t size, const wchar_t *format, unsigned width, int precision, double value); diff --git a/fmt/format.h b/fmt/format.h index 6ee9d2a2..561a9e07 100644 --- a/fmt/format.h +++ b/fmt/format.h @@ -28,6 +28,7 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ +#define FMT_INCLUDE #include #include #include @@ -39,11 +40,26 @@ #include #include #include // for std::pair +#undef FMT_INCLUDE // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 40000 +#define FMT_VERSION 40100 -#ifdef _SECURE_SCL +#if defined(__has_include) +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif + +#if (FMT_HAS_INCLUDE() && __cplusplus > 201402L) || \ + (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) +# include +# define FMT_HAS_STRING_VIEW 1 +#else +# define FMT_HAS_STRING_VIEW 0 +#endif + +#if defined _SECURE_SCL && _SECURE_SCL # define FMT_SECURE_SCL _SECURE_SCL #else # define FMT_SECURE_SCL 0 @@ -97,7 +113,9 @@ typedef __int64 intmax_t; # define FMT_HAS_GXX_CXX11 1 # endif #else +# define FMT_GCC_VERSION 0 # define FMT_GCC_EXTENSION +# define FMT_HAS_GXX_CXX11 0 #endif #if defined(__INTEL_COMPILER) @@ -135,6 +153,32 @@ typedef __int64 intmax_t; # define FMT_HAS_CPP_ATTRIBUTE(x) 0 #endif +#if FMT_HAS_CPP_ATTRIBUTE(maybe_unused) +# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED +// VC++ 1910 support /std: option and that will set _MSVC_LANG macro +// Clang with Microsoft CodeGen doesn't define _MSVC_LANG macro +#elif defined(_MSVC_LANG) && _MSVC_LANG > 201402 && _MSC_VER >= 1910 +# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED +#endif + +#ifdef FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED +# define FMT_MAYBE_UNUSED [[maybe_unused]] +// g++/clang++ also support [[gnu::unused]]. However, we don't use it. +#elif defined(__GNUC__) +# define FMT_MAYBE_UNUSED __attribute__((unused)) +#else +# define FMT_MAYBE_UNUSED +#endif + +// Use the compiler's attribute noreturn +#if defined(__MINGW32__) || defined(__MINGW64__) +# define FMT_NORETURN __attribute__((noreturn)) +#elif FMT_HAS_CPP_ATTRIBUTE(noreturn) && __cplusplus >= 201103L +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + #ifndef FMT_USE_VARIADIC_TEMPLATES // Variadic templates are available in GCC since version 4.4 // (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++ @@ -156,6 +200,12 @@ typedef __int64 intmax_t; # endif #endif +#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 +# define FMT_USE_ALLOCATOR_TRAITS 1 +#else +# define FMT_USE_ALLOCATOR_TRAITS 0 +#endif + // Check if exceptions are disabled. #if defined(__GNUC__) && !defined(__EXCEPTIONS) # define FMT_EXCEPTIONS 0 @@ -262,11 +312,14 @@ typedef __int64 intmax_t; // makes the fmt::literals implementation easier. However, an explicit check // for variadic templates is added here just in case. // For Intel's compiler both it and the system gcc/msc must support UDLs. -# define FMT_USE_USER_DEFINED_LITERALS \ - FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \ +# if FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \ (FMT_HAS_FEATURE(cxx_user_literals) || \ (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900) && \ (!defined(FMT_ICC_VERSION) || FMT_ICC_VERSION >= 1500) +# define FMT_USE_USER_DEFINED_LITERALS 1 +# else +# define FMT_USE_USER_DEFINED_LITERALS 0 +# endif #endif #ifndef FMT_USE_EXTERN_TEMPLATES @@ -305,7 +358,10 @@ typedef __int64 intmax_t; namespace fmt { namespace internal { -# pragma intrinsic(_BitScanReverse) +// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning +# ifndef __clang__ +# pragma intrinsic(_BitScanReverse) +# endif inline uint32_t clz(uint32_t x) { unsigned long r = 0; _BitScanReverse(&r, x); @@ -319,7 +375,8 @@ inline uint32_t clz(uint32_t x) { } # define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) -# ifdef _WIN64 +// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning +# if defined(_WIN64) && !defined(__clang__) # pragma intrinsic(_BitScanReverse64) # endif @@ -505,6 +562,26 @@ class BasicStringRef { const std::basic_string, Allocator> &s) : data_(s.c_str()), size_(s.size()) {} +#if FMT_HAS_STRING_VIEW + /** + \rst + Constructs a string reference from a ``std::basic_string_view`` object. + \endrst + */ + BasicStringRef( + const std::basic_string_view> &s) + : data_(s.data()), size_(s.size()) {} + + /** + \rst + Converts a string reference to an ``std::string_view`` object. + \endrst + */ + explicit operator std::basic_string_view() const FMT_NOEXCEPT { + return std::basic_string_view(data_, size_); + } +#endif + /** \rst Converts a string reference to an ``std::string`` object. @@ -609,7 +686,7 @@ class FormatError : public std::runtime_error { explicit FormatError(CStringRef message) : std::runtime_error(message.c_str()) {} FormatError(const FormatError &ferr) : std::runtime_error(ferr) {} - FMT_API ~FormatError() FMT_DTOR_NOEXCEPT; + FMT_API ~FormatError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; }; namespace internal { @@ -726,7 +803,7 @@ template template void Buffer::append(const U *begin, const U *end) { FMT_ASSERT(end >= begin, "negative value"); - std::size_t new_size = size_ + (end - begin); + std::size_t new_size = size_ + static_cast(end - begin); if (new_size > capacity_) grow(new_size); std::uninitialized_copy(begin, end, @@ -754,7 +831,7 @@ class MemoryBuffer : private Allocator, public Buffer { public: explicit MemoryBuffer(const Allocator &alloc = Allocator()) : Allocator(alloc), Buffer(data_, SIZE) {} - ~MemoryBuffer() { deallocate(); } + ~MemoryBuffer() FMT_OVERRIDE { deallocate(); } #if FMT_USE_RVALUE_REFERENCES private: @@ -798,7 +875,12 @@ void MemoryBuffer::grow(std::size_t size) { std::size_t new_capacity = this->capacity_ + this->capacity_ / 2; if (size > new_capacity) new_capacity = size; +#if FMT_USE_ALLOCATOR_TRAITS + T *new_ptr = + std::allocator_traits::allocate(*this, new_capacity, FMT_NULL); +#else T *new_ptr = this->allocate(new_capacity, FMT_NULL); +#endif // The following code doesn't throw, so the raw pointer above doesn't leak. std::uninitialized_copy(this->ptr_, this->ptr_ + this->size_, make_ptr(new_ptr, new_capacity)); @@ -916,7 +998,7 @@ struct IntTraits { TypeSelector::digits <= 32>::Type MainType; }; -FMT_API void report_unknown_type(char code, const char *type); +FMT_API FMT_NORETURN void report_unknown_type(char code, const char *type); // Static data is placed in this class template to allow header-only // configuration. @@ -1155,17 +1237,17 @@ T &get(); Yes &convert(fmt::ULongLong); No &convert(...); -template +template struct ConvertToIntImpl { enum { value = ENABLE_CONVERSION }; }; -template +template struct ConvertToIntImpl2 { enum { value = false }; }; -template +template struct ConvertToIntImpl2 { enum { // Don't convert numeric types. @@ -1173,7 +1255,7 @@ struct ConvertToIntImpl2 { }; }; -template +template struct ConvertToInt { enum { enable_conversion = sizeof(fmt::internal::convert(get())) == sizeof(Yes) @@ -1190,16 +1272,16 @@ FMT_DISABLE_CONVERSION_TO_INT(float); FMT_DISABLE_CONVERSION_TO_INT(double); FMT_DISABLE_CONVERSION_TO_INT(long double); -template +template struct EnableIf {}; -template +template struct EnableIf { typedef T type; }; -template +template struct Conditional { typedef T type; }; -template +template struct Conditional { typedef F type; }; // For bcc32 which doesn't understand ! in template arguments. @@ -1248,9 +1330,9 @@ inline fmt::StringRef thousands_sep(...) { return ""; } typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED #endif -template -void format_arg(Formatter &, const Char *, const T &) { - FMT_STATIC_ASSERT(FalseType::value, +template +void format_arg(Formatter&, ...) { + FMT_STATIC_ASSERT(FalseType::value, "Cannot format argument. To enable the use of ostream " "operator<< include fmt/ostream.h. Otherwise provide " "an overload of format_arg."); @@ -1283,6 +1365,9 @@ class MakeValue : public Arg { MakeValue(typename WCharHelper::Unsupported); MakeValue(typename WCharHelper::Unsupported); MakeValue(typename WCharHelper::Unsupported); +#if FMT_HAS_STRING_VIEW + MakeValue(typename WCharHelper::Unsupported); +#endif MakeValue(typename WCharHelper::Unsupported); void set_string(StringRef str) { @@ -1352,6 +1437,20 @@ class MakeValue : public Arg { FMT_MAKE_VALUE(unsigned char, uint_value, UINT) FMT_MAKE_VALUE(char, int_value, CHAR) +#if __cplusplus >= 201103L + template < + typename T, + typename = typename std::enable_if< + std::is_enum::value && ConvertToInt::value>::type> + MakeValue(T value) { int_value = value; } + + template < + typename T, + typename = typename std::enable_if< + std::is_enum::value && ConvertToInt::value>::type> + static uint64_t type(T) { return Arg::INT; } +#endif + #if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) MakeValue(typename WCharHelper::Supported value) { int_value = value; @@ -1370,6 +1469,9 @@ class MakeValue : public Arg { FMT_MAKE_VALUE(unsigned char *, ustring.value, CSTRING) FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING) FMT_MAKE_STR_VALUE(const std::string &, STRING) +#if FMT_HAS_STRING_VIEW + FMT_MAKE_STR_VALUE(const std::string_view &, STRING) +#endif FMT_MAKE_STR_VALUE(StringRef, STRING) FMT_MAKE_VALUE_(CStringRef, string.value, CSTRING, value.c_str()) @@ -1382,6 +1484,9 @@ class MakeValue : public Arg { FMT_MAKE_WSTR_VALUE(wchar_t *, WSTRING) FMT_MAKE_WSTR_VALUE(const wchar_t *, WSTRING) FMT_MAKE_WSTR_VALUE(const std::wstring &, WSTRING) +#if FMT_HAS_STRING_VIEW + FMT_MAKE_WSTR_VALUE(const std::wstring_view &, WSTRING) +#endif FMT_MAKE_WSTR_VALUE(WStringRef, WSTRING) FMT_MAKE_VALUE(void *, pointer, POINTER) @@ -1447,7 +1552,7 @@ class RuntimeError : public std::runtime_error { protected: RuntimeError() : std::runtime_error("") {} RuntimeError(const RuntimeError &rerr) : std::runtime_error(rerr) {} - FMT_API ~RuntimeError() FMT_DTOR_NOEXCEPT; + FMT_API ~RuntimeError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; }; template @@ -1921,7 +2026,7 @@ class ArgMap { MapType map_; public: - FMT_API void init(const ArgList &args); + void init(const ArgList &args); const internal::Arg *find(const fmt::BasicStringRef &name) const { // The list is unsorted, so just return the first matching name. @@ -1934,6 +2039,51 @@ class ArgMap { } }; +template +void ArgMap::init(const ArgList &args) { + if (!map_.empty()) + return; + typedef internal::NamedArg NamedArg; + const NamedArg *named_arg = FMT_NULL; + bool use_values = + args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE; + if (use_values) { + for (unsigned i = 0;/*nothing*/; ++i) { + internal::Arg::Type arg_type = args.type(i); + switch (arg_type) { + case internal::Arg::NONE: + return; + case internal::Arg::NAMED_ARG: + named_arg = static_cast(args.values_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } + return; + } + for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) { + internal::Arg::Type arg_type = args.type(i); + if (arg_type == internal::Arg::NAMED_ARG) { + named_arg = static_cast(args.args_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + } + } + for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) { + switch (args.args_[i].type) { + case internal::Arg::NONE: + return; + case internal::Arg::NAMED_ARG: + named_arg = static_cast(args.args_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } +} + template class ArgFormatterBase : public ArgVisitor { private: @@ -2220,7 +2370,8 @@ struct ArgArray; template struct ArgArray { - typedef Value Type[N > 0 ? N : 1]; + // '+' is used to silence GCC -Wduplicated-branches warning. + typedef Value Type[N > 0 ? N : +1]; template static Value make(const T &value) { @@ -2410,7 +2561,7 @@ class SystemError : public internal::RuntimeError { FMT_DEFAULTED_COPY_CTOR(SystemError) FMT_VARIADIC_CTOR(SystemError, init, int, CStringRef) - FMT_API ~SystemError() FMT_DTOR_NOEXCEPT; + FMT_API ~SystemError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; int error_code() const { return error_code_; } }; @@ -3483,10 +3634,10 @@ void arg(WStringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; #define FMT_GET_ARG_NAME(type, index) arg##index #if FMT_USE_VARIADIC_TEMPLATES -# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ +# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \ template \ ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ - const Args & ... args) { \ + const Args & ... args) Const { \ typedef fmt::internal::ArgArray ArgArray; \ typename ArgArray::Type array{ \ ArgArray::template make >(args)...}; \ @@ -3496,35 +3647,35 @@ void arg(WStringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; #else // Defines a wrapper for a function taking __VA_ARGS__ arguments // and n additional arguments of arbitrary types. -# define FMT_WRAP(Char, ReturnType, func, call, n, ...) \ +# define FMT_WRAP(Const, Char, ReturnType, func, call, n, ...) \ template \ inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ - FMT_GEN(n, FMT_MAKE_ARG)) { \ + FMT_GEN(n, FMT_MAKE_ARG)) Const { \ fmt::internal::ArgArray::Type arr; \ FMT_GEN(n, FMT_ASSIGN_##Char); \ call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \ fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), arr)); \ } -# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ - inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) { \ +# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) Const { \ call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \ } \ - FMT_WRAP(Char, ReturnType, func, call, 1, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 2, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 3, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 4, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 5, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 6, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 7, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 8, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 9, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 10, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 11, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 12, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 13, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 14, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 15, __VA_ARGS__) + FMT_WRAP(Const, Char, ReturnType, func, call, 1, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 2, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 3, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 4, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 5, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 6, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 7, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 8, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 9, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 10, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 11, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 12, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 13, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 14, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 15, __VA_ARGS__) #endif // FMT_USE_VARIADIC_TEMPLATES /** @@ -3555,10 +3706,16 @@ void arg(WStringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; \endrst */ #define FMT_VARIADIC(ReturnType, func, ...) \ - FMT_VARIADIC_(char, ReturnType, func, return func, __VA_ARGS__) + FMT_VARIADIC_(, char, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_VARIADIC_CONST(ReturnType, func, ...) \ + FMT_VARIADIC_(const, char, ReturnType, func, return func, __VA_ARGS__) #define FMT_VARIADIC_W(ReturnType, func, ...) \ - FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__) + FMT_VARIADIC_(, wchar_t, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_VARIADIC_CONST_W(ReturnType, func, ...) \ + FMT_VARIADIC_(const, wchar_t, ReturnType, func, return func, __VA_ARGS__) #define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id) @@ -3601,17 +3758,19 @@ template unsigned parse_nonnegative_int(const Char *&s) { assert('0' <= *s && *s <= '9'); unsigned value = 0; - do { - unsigned new_value = value * 10 + (*s++ - '0'); - // Check if value wrapped around. - if (new_value < value) { - value = (std::numeric_limits::max)(); - break; - } - value = new_value; - } while ('0' <= *s && *s <= '9'); // Convert to unsigned to prevent a warning. unsigned max_int = (std::numeric_limits::max)(); + unsigned big = max_int / 10; + do { + // Check for overflow. + if (value > big) { + value = max_int + 1; + break; + } + value = value * 10 + (*s - '0'); + ++s; + } while ('0' <= *s && *s <= '9'); + // Convert to unsigned to prevent a warning. if (value > max_int) FMT_THROW(FormatError("number is too big")); return value; @@ -3783,7 +3942,8 @@ const Char *BasicFormatter::format( default: FMT_THROW(FormatError("width is not integer")); } - if (value > (std::numeric_limits::max)()) + unsigned max_int = (std::numeric_limits::max)(); + if (value > max_int) FMT_THROW(FormatError("number is too big")); spec.width_ = static_cast(value); } @@ -3821,7 +3981,8 @@ const Char *BasicFormatter::format( default: FMT_THROW(FormatError("precision is not integer")); } - if (value > (std::numeric_limits::max)()) + unsigned max_int = (std::numeric_limits::max)(); + if (value > max_int) FMT_THROW(FormatError("number is too big")); spec.precision_ = static_cast(value); } else { diff --git a/fmt/ostream.h b/fmt/ostream.h index 84a02d17..6848aac2 100644 --- a/fmt/ostream.h +++ b/fmt/ostream.h @@ -52,13 +52,15 @@ Yes &convert(std::ostream &); struct DummyStream : std::ostream { DummyStream(); // Suppress a bogus warning in MSVC. + // Hide all operator<< overloads from std::ostream. - void operator<<(Null<>); + template + typename EnableIf::type operator<<(const T &); }; No &operator<<(std::ostream &, int); -template +template struct ConvertToIntImpl { // Convert to int only if T doesn't have an overloaded operator<<. enum { @@ -78,6 +80,7 @@ void format_arg(BasicFormatter &f, internal::FormatBuf format_buf(buffer); std::basic_ostream output(&format_buf); + output.exceptions(std::ios_base::failbit | std::ios_base::badbit); output << value; BasicStringRef str(&buffer[0], buffer.size()); diff --git a/fmt/printf.h b/fmt/printf.h index 30cbc49b..46205a78 100644 --- a/fmt/printf.h +++ b/fmt/printf.h @@ -110,7 +110,7 @@ class ArgConverter : public ArgVisitor, void> { visit_any_int(value); } - void visit_char(char value) { + void visit_char(int value) { if (type_ != 's') visit_any_int(value); } @@ -125,7 +125,7 @@ class ArgConverter : public ArgVisitor, void> { using internal::Arg; typedef typename internal::Conditional< is_same::value, U, T>::type TargetType; - if (sizeof(TargetType) <= sizeof(int)) { + if (const_check(sizeof(TargetType) <= sizeof(int))) { // Extra casts are used to silence warnings. if (is_signed) { arg_.type = Arg::INT; diff --git a/fmt/string.h b/fmt/string.h index ccf46ee1..05996eb5 100644 --- a/fmt/string.h +++ b/fmt/string.h @@ -7,6 +7,10 @@ For the license information refer to format.h. */ +#ifdef FMT_INCLUDE +# error "Add the fmt's parent directory and not fmt itself to includes." +#endif + #ifndef FMT_STRING_H_ #define FMT_STRING_H_ @@ -121,6 +125,24 @@ std::string to_string(const T &value) { w << value; return w.str(); } + +/** + \rst + Converts *value* to ``std::wstring`` using the default format for type *T*. + + **Example**:: + + #include "fmt/string.h" + + std::wstring answer = fmt::to_wstring(42); + \endrst + */ +template +std::wstring to_wstring(const T &value) { + fmt::WMemoryWriter w; + w << value; + return w.str(); +} } #endif // FMT_STRING_H_ diff --git a/support/manage.py b/support/manage.py index 1da371d4..baafe805 100755 --- a/support/manage.py +++ b/support/manage.py @@ -8,7 +8,7 @@ Usage: """ from __future__ import print_function -import datetime, docopt, fileinput, json, os +import datetime, docopt, errno, fileinput, json, os import re, requests, shutil, sys, tempfile from contextlib import contextmanager from distutils.version import LooseVersion @@ -80,6 +80,7 @@ def create_build_env(): import build env.build_dir = 'build' + env.versions = build.versions # Virtualenv and repos are cached to speed up builds. build.create_build_env(os.path.join(env.build_dir, 'virtualenv')) @@ -113,7 +114,7 @@ def update_site(env): doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io')) doc_repo.update('git@github.com:fmtlib/fmtlib.github.io') - for version in ['1.0.0', '1.1.0', '2.0.0', '3.0.0']: + for version in env.versions: clean_checkout(env.fmt_repo, version) target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc') # Remove the old theme. @@ -165,7 +166,11 @@ def update_site(env): os.symlink(target, link) # Copy docs to the website. version_doc_dir = os.path.join(doc_repo.dir, version) - shutil.rmtree(version_doc_dir) + try: + shutil.rmtree(version_doc_dir) + except OSError as e: + if e.errno != errno.ENOENT: + raise shutil.move(html_dir, version_doc_dir) @@ -204,27 +209,45 @@ def release(args): line = '-' * title_len + '\n' title_len = 0 sys.stdout.write(line) - # TODO: add new version to manage.py + + # Add the version to the build script. + script = os.path.join('doc', 'build.py') + script_path = os.path.join(fmt_repo.dir, script) + for line in fileinput.input(script_path, inplace=True): + m = re.match(r'( *versions = )\[(.+)\]', line) + if m: + line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version) + sys.stdout.write(line) + fmt_repo.checkout('-B', 'release') - fmt_repo.add(changelog, cmakelists) + fmt_repo.add(changelog, cmakelists, script) fmt_repo.commit('-m', 'Update version') # Build the docs and package. run = Runner(fmt_repo.dir) run('cmake', '.') run('make', 'doc', 'package_source') - update_site(env) # Create a release on GitHub. fmt_repo.push('origin', 'release') + params = {'access_token': os.getenv('FMT_TOKEN')} r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', - params={'access_token': os.getenv('FMT_TOKEN')}, + params=params, data=json.dumps({'tag_name': version, 'target_commitish': 'release', 'body': changes, 'draft': True})) if r.status_code != 201: raise Exception('Failed to create a release ' + str(r)) + id = r.json()['id'] + uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' + package = 'fmt-{}.zip'.format(version) + with open('build/fmt/' + package, 'rb') as f: + r = requests.post( + '{}/{}/assets?name={}'.format(uploads_url, id, package), + params=params, files={package: f}) + if r.status_code != 201: + raise Exception('Failed to upload an asset ' + str(r)) if __name__ == '__main__': diff --git a/support/travis-build.py b/support/travis-build.py index 91017792..c4b8daf1 100755 --- a/support/travis-build.py +++ b/support/travis-build.py @@ -30,7 +30,7 @@ def install_dependencies(): '| sudo tee /etc/apt/sources.list.d/nodesource.list', shell=True) check_call(['sudo', 'apt-get', 'update']) check_call(['sudo', 'apt-get', 'install', 'python-virtualenv', 'nodejs']) - check_call(['npm', 'install', '-g', 'less', 'less-plugin-clean-css']) + check_call(['sudo', 'npm', 'install', '-g', 'less@2.6.1', 'less-plugin-clean-css']) deb_file = 'doxygen_1.8.6-2_amd64.deb' urllib.urlretrieve('http://mirrors.kernel.org/ubuntu/pool/main/d/doxygen/' + deb_file, deb_file) diff --git a/test/format-test.cc b/test/format-test.cc index 6388d5a5..f512ef48 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -151,16 +151,31 @@ TEST(StringRefTest, Ctor) { EXPECT_STREQ("defg", StringRef(std::string("defg")).data()); EXPECT_EQ(4u, StringRef(std::string("defg")).size()); + +#if FMT_HAS_STRING_VIEW + EXPECT_STREQ("hijk", StringRef(std::string_view("hijk")).data()); + EXPECT_EQ(4u, StringRef(std::string_view("hijk")).size()); +#endif } TEST(StringRefTest, ConvertToString) { std::string s = StringRef("abc").to_string(); EXPECT_EQ("abc", s); + +#if FMT_HAS_STRING_VIEW + StringRef str_ref("defg"); + std::string_view sv = static_cast(str_ref); + EXPECT_EQ("defg", sv); +#endif } TEST(CStringRefTest, Ctor) { EXPECT_STREQ("abc", CStringRef("abc").c_str()); EXPECT_STREQ("defg", CStringRef(std::string("defg")).c_str()); + +#if FMT_HAS_STRING_VIEW + EXPECT_STREQ("hijk", CStringRef(std::string_view("hijk")).c_str()); +#endif } #if FMT_USE_TYPE_TRAITS @@ -1378,6 +1393,12 @@ TEST(FormatterTest, FormatCStringRef) { EXPECT_EQ("test", format("{0}", CStringRef("test"))); } +#if FMT_HAS_STRING_VIEW +TEST(FormatterTest, FormatStringView) { + EXPECT_EQ("test", format("{0}", std::string_view("test"))); +} +#endif + void format_arg(fmt::BasicFormatter &f, const char *, const Date &d) { f.writer() << d.year() << '-' << d.month() << '-' << d.day(); } @@ -1609,6 +1630,24 @@ TEST(FormatTest, FormatMessageExample) { format_message(42, "{} happened", "something")); } +class test_class +{ +public: + std::string format_message(int id, const char *format,const fmt::ArgList &args) const { + MemoryWriter w; + w.write("[{}] ", id); + w.write(format, args); + return w.str(); + } + FMT_VARIADIC_CONST(std::string, format_message, int, const char *) +}; + +TEST(FormatTest, ConstFormatMessage) { + test_class c; + EXPECT_EQ("[42] something happened", + c.format_message(42, "{} happened", "something")); +} + #if FMT_USE_VARIADIC_TEMPLATES template void print_error(const char *file, int line, const char *format, @@ -1660,6 +1699,14 @@ TEST(FormatTest, Enum) { EXPECT_EQ("0", fmt::format("{}", A)); } +#if __cplusplus >= 201103L +enum TestFixedEnum : short { B }; + +TEST(FormatTest, FixedEnum) { + EXPECT_EQ("0", fmt::format("{}", B)); +} +#endif + class MockArgFormatter : public fmt::internal::ArgFormatterBase { public: diff --git a/test/mock-allocator.h b/test/mock-allocator.h index 34b9c11a..5f0f0bc4 100644 --- a/test/mock-allocator.h +++ b/test/mock-allocator.h @@ -36,7 +36,7 @@ class MockAllocator { MockAllocator() {} MockAllocator(const MockAllocator &) {} typedef T value_type; - MOCK_METHOD2_T(allocate, T *(std::size_t n, const T *h)); + MOCK_METHOD2_T(allocate, T *(std::size_t n, const void *h)); MOCK_METHOD2_T(deallocate, void (T *p, std::size_t n)); }; @@ -78,8 +78,12 @@ class AllocatorRef { Allocator *get() const { return alloc_; } - value_type *allocate(std::size_t n, const value_type *h) { + value_type *allocate(std::size_t n, const void *h) { +#if FMT_USE_ALLOCATOR_TRAITS + return std::allocator_traits::allocate(*alloc_, n, h); +#else return alloc_->allocate(n, h); +#endif } void deallocate(value_type *p, std::size_t n) { alloc_->deallocate(p, n); } }; diff --git a/test/ostream-test.cc b/test/ostream-test.cc index 4081b43f..486981bc 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -172,3 +172,18 @@ TEST(OStreamTest, WriteToOStreamMaxSize) { } while (size != 0); fmt::internal::write(os, w); } + +struct ConvertibleToInt { + template + operator ValueType() const { + return 0; + } + + friend std::ostream &operator<<(std::ostream &o, ConvertibleToInt) { + return o << "foo"; + } +}; + +TEST(FormatTest, FormatConvertibleToInt) { + EXPECT_EQ("foo", fmt::format("{}", ConvertibleToInt())); +} diff --git a/test/string-test.cc b/test/string-test.cc index 10e537b7..06f55f65 100644 --- a/test/string-test.cc +++ b/test/string-test.cc @@ -78,3 +78,7 @@ TEST(StringWriterTest, WString) { TEST(StringTest, ToString) { EXPECT_EQ("42", fmt::to_string(42)); } + +TEST(StringTest, ToWString) { + EXPECT_EQ(L"42", fmt::to_wstring(42)); +} diff --git a/test/util-test.cc b/test/util-test.cc index a3882558..6617b857 100644 --- a/test/util-test.cc +++ b/test/util-test.cc @@ -837,8 +837,21 @@ TEST(UtilTest, FormatSystemError) { fmt::format_system_error(message, EDOM, "test"); EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), message.str()); message.clear(); - fmt::format_system_error( - message, EDOM, fmt::StringRef(0, std::numeric_limits::max())); + + // Check if std::allocator throws on allocating max size_t / 2 chars. + size_t max_size = std::numeric_limits::max() / 2; + bool throws_on_alloc = false; + try { + std::allocator alloc; + alloc.deallocate(alloc.allocate(max_size), max_size); + } catch (std::bad_alloc) { + throws_on_alloc = true; + } + if (!throws_on_alloc) { + fmt::print("warning: std::allocator allocates {} chars", max_size); + return; + } + fmt::format_system_error(message, EDOM, fmt::StringRef(0, max_size)); EXPECT_EQ(fmt::format("error {}", EDOM), message.str()); }