From d2bf93fe2289f8ed1c45f04e257804401870c080 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 19 May 2018 07:13:06 -0700 Subject: [PATCH 1/6] Update changelog --- ChangeLog.rst | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 8cd24414..b987c849 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -52,6 +52,9 @@ :12:45: error: expression '' is not a constant expression throw format_error("invalid specifier"); + Compile-time checks require relaxed ``constexpr`` (C++14 feature) support. If + the latter is not available, checks will be performed at runtime. + * Added `iterator support `_: @@ -64,8 +67,8 @@ fmt::format_to(std::back_inserter(out), "{}", 42); * Added the `formatted_size - `_ function for - computing output size: + `_ + function for computing the output size: .. code:: c++ @@ -154,17 +157,26 @@ * Implemented more efficient handling of large number of format arguments. +* Added debug postfix ``d`` to the `fmt`` library name + (`#636 `_). + * Removed unnecessary ``fmt/`` prefix in includes (`#397 `_). Thanks `@chronoxor (Ivan Shynkarenka) `_. -* Renamed ``CHAR_WIDTH`` to ``CHAR_SIZE`` to avoid collision with ISO/IEC TS - 18661-1:2014 macro. +* Added qmake project file ``support/fmt.pro`` + (`#641 `_). + Thanks `@cowo78 (Giuseppe Corbelli) `_. + +* Removed ``FMT_CPPFORMAT`` CMake option. * Fixed a name conflict with the macro ``CHAR_WIDTH`` in glibc (`#616 `_). Thanks `@aroig (Abdó Roig-Maranges) `_. +* Fixed handling of nested braces in ``fmt::join`` + (`#638 `_). + * Added ``SOURCELINK_SUFFIX`` for compatibility with Sphinx 1.5 (`#497 `_). Thanks `@ginggs (Graham Inggs) `_. @@ -173,6 +185,18 @@ (`#626 `_). Thanks `@aroig (Abdó Roig-Maranges) `_. +* Fixed a warning about unreachable code + (`#640 `_). + Thanks `@peterbell10 `_. + +* Worked around an MSVC bug and fixed several warnings + (`#653 `_). + Thanks `@alabuzhev (Alex Alabuzhev) `_. + +* Fixed compilation with ``-fno-exceptions`` + (`#655 `_). + Thanks `@chenxiaolong (Andrew Gunnerson) `_. + 4.1.0 - 2017-12-20 ------------------ @@ -234,7 +258,7 @@ `@thelostt (Mário Feroldi) `_, and `@Manu343726 (Manu Sánchez) `_. -* Improved CMake: Used GNUInstallDirs to set installation location +* Improved CMake: Used ``GNUInstallDirs`` to set installation location (`#610 `_) and fixed warnings (`#536 `_ and `#556 `_). From d940fa679c0ca62889f919c5bb3686169ca4c740 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 19 May 2018 07:14:13 -0700 Subject: [PATCH 2/6] Disable unsafe implicit conversion to std::string (#729) --- include/fmt/core.h | 5 +++-- test/compile-test/CMakeLists.txt | 11 +++++++++-- test/format-test.cc | 10 ---------- test/util-test.cc | 2 +- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 2625d86b..77ff089b 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -629,8 +629,9 @@ inline typename std::enable_if< template inline typename std::enable_if< !convert_to_int::value && - !std::is_convertible>::value && - !std::is_convertible>::value, + !std::is_convertible>::value, + // Implicit conversion to std::string is not handled here because it's + // unsafe: https://github.com/fmtlib/fmt/issues/729 typed_value>::type make_value(const T &val) { return val; } diff --git a/test/compile-test/CMakeLists.txt b/test/compile-test/CMakeLists.txt index 38038ede..dda1fb96 100644 --- a/test/compile-test/CMakeLists.txt +++ b/test/compile-test/CMakeLists.txt @@ -9,8 +9,7 @@ set(CMAKE_REQUIRED_FLAGS ${CPP14_FLAG}) function (generate_source result fragment) set(${result} " #define FMT_HEADER_ONLY 1 - #include \"fmt/posix.h\" - #include \"fmt/ostream.h\" + #include \"fmt/format.h\" int main() { ${fragment} } @@ -58,6 +57,14 @@ expect_compile_error("fmt::format(\"{}\", L\"foo\");") # mixing UTF-8 with UTF-16/32 can result in an invalid output. expect_compile_error("fmt::format(L\"{}\", \"foo\");") +# Formatting a wide string with a narrow format string is forbidden. +expect_compile_error(" + struct S { + operator std::string() const { return std::string(); } + }; + fmt::format(\"{}\", S()); +") + # Make sure that compiler features detected in the header # match the features detected in CMake. if (SUPPORTS_USER_DEFINED_LITERALS) diff --git a/test/format-test.cc b/test/format-test.cc index 4a6533f5..d9a20919 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1072,16 +1072,6 @@ TEST(FormatterTest, FormatStdStringView) { } #endif -struct ConvertibleToString { - std::string s; - ConvertibleToString() : s("foo") {} - operator const std::string &() const { return s; } -}; - -TEST(FormatterTest, FormatConvertibleToString) { - EXPECT_EQ("foo", format("{}", ConvertibleToString())); -} - struct ConvertibleToStringView { operator fmt::string_view() const { return "foo"; } }; diff --git a/test/util-test.cc b/test/util-test.cc index 544d933d..5bba7736 100644 --- a/test/util-test.cc +++ b/test/util-test.cc @@ -402,7 +402,7 @@ TEST(UtilTest, BitCast) { uint32_t u[2]; }; auto s = fmt::internal::bit_cast(uint64_t(42)); - EXPECT_EQ(fmt::internal::bit_cast(s), 42); + EXPECT_EQ(fmt::internal::bit_cast(s), 42u); s = fmt::internal::bit_cast(uint64_t(~0ull)); EXPECT_EQ(fmt::internal::bit_cast(s), ~0ull); } From 69823bf85200ca22e51067cf34e3c009530c1ac4 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 19 May 2018 08:57:31 -0700 Subject: [PATCH 3/6] Improve naming consistency --- include/fmt/format-inl.h | 8 ++++---- include/fmt/format.h | 16 ++++++++-------- include/fmt/ostream.h | 6 +++--- include/fmt/posix.h | 34 +++++++++++++++++----------------- include/fmt/time.h | 21 ++++++++++++--------- src/posix.cc | 12 ++++++------ test/format-test.cc | 20 ++++++++++---------- test/gtest-extra-test.cc | 18 +++++++++--------- test/posix-mock-test.cc | 14 +++++++------- test/posix-test.cc | 32 ++++++++++++++++---------------- test/util.cc | 4 ++-- test/util.h | 2 +- 12 files changed, 95 insertions(+), 92 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index f5c26d2f..19d5dd8b 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -113,14 +113,14 @@ int safe_strerror( int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); - class StrError { + class dispatcher { private: int error_code_; char *&buffer_; std::size_t buffer_size_; // A noop assignment operator to avoid bogus warnings. - void operator=(const StrError &) {} + void operator=(const dispatcher &) {} // Handle the result of XSI-compliant version of strerror_r. int handle(int result) { @@ -157,14 +157,14 @@ int safe_strerror( } public: - StrError(int err_code, char *&buf, std::size_t buf_size) + dispatcher(int err_code, char *&buf, std::size_t buf_size) : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); } }; - return StrError(error_code, buffer, buffer_size).run(); + return dispatcher(error_code, buffer, buffer_size).run(); } void format_error_code(internal::buffer &out, int error_code, diff --git a/include/fmt/format.h b/include/fmt/format.h index 3d35a18c..895cf925 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2974,7 +2974,7 @@ FMT_API void report_windows_error(int error_code, #endif /** Fast integer formatter. */ -class FormatInt { +class format_int { private: // Buffer should be large enough to hold all digits (digits10 + 1), // a sign and a null character. @@ -3015,12 +3015,12 @@ class FormatInt { } public: - explicit FormatInt(int value) { format_signed(value); } - explicit FormatInt(long value) { format_signed(value); } - explicit FormatInt(long long value) { format_signed(value); } - explicit FormatInt(unsigned value) : str_(format_decimal(value)) {} - explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {} - explicit FormatInt(unsigned long long value) : str_(format_decimal(value)) {} + explicit format_int(int value) { format_signed(value); } + explicit format_int(long value) { format_signed(value); } + explicit format_int(long long value) { format_signed(value); } + explicit format_int(unsigned value) : str_(format_decimal(value)) {} + explicit format_int(unsigned long value) : str_(format_decimal(value)) {} + explicit format_int(unsigned long long value) : str_(format_decimal(value)) {} /** Returns the number of characters written to the output buffer. */ std::size_t size() const { @@ -3647,7 +3647,7 @@ FMT_END_NAMESPACE #include // A compile-time error because 'd' is an invalid specifier for strings. - std::string s = fmt::format(fmt("{:d}"), "foo"); + std::string s = format(fmt("{:d}"), "foo"); \endrst */ # define fmt(s) FMT_STRING(s) diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index fdbe1698..14619364 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -15,7 +15,7 @@ FMT_BEGIN_NAMESPACE namespace internal { template -class FormatBuf : public std::basic_streambuf { +class formatbuf : public std::basic_streambuf { private: typedef typename std::basic_streambuf::int_type int_type; typedef typename std::basic_streambuf::traits_type traits_type; @@ -23,7 +23,7 @@ class FormatBuf : public std::basic_streambuf { basic_buffer &buffer_; public: - FormatBuf(basic_buffer &buffer) : buffer_(buffer) {} + formatbuf(basic_buffer &buffer) : buffer_(buffer) {} protected: // The put-area is actually always empty. This makes the implementation @@ -90,7 +90,7 @@ void write(std::basic_ostream &os, basic_buffer &buf) { template void format_value(basic_buffer &buffer, const T &value) { - internal::FormatBuf format_buf(buffer); + internal::formatbuf format_buf(buffer); std::basic_ostream output(&format_buf); output.exceptions(std::ios_base::failbit | std::ios_base::badbit); output << value; diff --git a/include/fmt/posix.h b/include/fmt/posix.h index 67f5b91f..89aea2a1 100644 --- a/include/fmt/posix.h +++ b/include/fmt/posix.h @@ -124,20 +124,20 @@ class ErrorCode { }; // A buffered file. -class BufferedFile { +class buffered_file { private: FILE *file_; friend class File; - explicit BufferedFile(FILE *f) : file_(f) {} + explicit buffered_file(FILE *f) : file_(f) {} public: - // Constructs a BufferedFile object which doesn't represent any file. - BufferedFile() FMT_NOEXCEPT : file_(FMT_NULL) {} + // Constructs a buffered_file object which doesn't represent any file. + buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {} // Destroys the object closing the file it represents if any. - FMT_API ~BufferedFile() FMT_DTOR_NOEXCEPT; + FMT_API ~buffered_file() FMT_DTOR_NOEXCEPT; #if !FMT_USE_RVALUE_REFERENCES // Emulate a move constructor and a move assignment operator if rvalue @@ -152,22 +152,22 @@ class BufferedFile { public: // A "move constructor" for moving from a temporary. - BufferedFile(Proxy p) FMT_NOEXCEPT : file_(p.file) {} + buffered_file(Proxy p) FMT_NOEXCEPT : file_(p.file) {} // A "move constructor" for moving from an lvalue. - BufferedFile(BufferedFile &f) FMT_NOEXCEPT : file_(f.file_) { + buffered_file(buffered_file &f) FMT_NOEXCEPT : file_(f.file_) { f.file_ = FMT_NULL; } // A "move assignment operator" for moving from a temporary. - BufferedFile &operator=(Proxy p) { + buffered_file &operator=(Proxy p) { close(); file_ = p.file; return *this; } // A "move assignment operator" for moving from an lvalue. - BufferedFile &operator=(BufferedFile &other) { + buffered_file &operator=(buffered_file &other) { close(); file_ = other.file_; other.file_ = FMT_NULL; @@ -175,7 +175,7 @@ public: } // Returns a proxy object for moving from a temporary: - // BufferedFile file = BufferedFile(...); + // buffered_file file = buffered_file(...); operator Proxy() FMT_NOEXCEPT { Proxy p = {file_}; file_ = FMT_NULL; @@ -184,14 +184,14 @@ public: #else private: - FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile); + FMT_DISALLOW_COPY_AND_ASSIGN(buffered_file); public: - BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : file_(other.file_) { + buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) { other.file_ = FMT_NULL; } - BufferedFile& operator=(BufferedFile &&other) { + buffered_file& operator=(buffered_file &&other) { close(); file_ = other.file_; other.file_ = FMT_NULL; @@ -200,7 +200,7 @@ public: #endif // Opens a file. - FMT_API BufferedFile(cstring_view filename, cstring_view mode); + FMT_API buffered_file(cstring_view filename, cstring_view mode); // Closes the file. FMT_API void close(); @@ -344,9 +344,9 @@ class File { // and writing respectively. FMT_API static void pipe(File &read_end, File &write_end); - // Creates a BufferedFile object associated with this file and detaches + // Creates a buffered_file object associated with this file and detaches // this File object from the file. - FMT_API BufferedFile fdopen(const char *mode); + FMT_API buffered_file fdopen(const char *mode); }; // Returns the memory page size. @@ -409,7 +409,7 @@ FMT_END_NAMESPACE #if !FMT_USE_RVALUE_REFERENCES namespace std { // For compatibility with C++98. -inline fmt::BufferedFile &move(fmt::BufferedFile &f) { return f; } +inline fmt::buffered_file &move(fmt::buffered_file &f) { return f; } inline fmt::File &move(fmt::File &f) { return f; } } #endif diff --git a/include/fmt/time.h b/include/fmt/time.h index 5976ea6e..58eb5ca3 100644 --- a/include/fmt/time.h +++ b/include/fmt/time.h @@ -22,11 +22,11 @@ inline null<> gmtime_s(...) { return null<>(); } // Thread-safe replacement for std::localtime inline std::tm localtime(std::time_t time) { - struct LocalTime { + struct dispatcher { std::time_t time_; std::tm tm_; - LocalTime(std::time_t t): time_(t) {} + dispatcher(std::time_t t): time_(t) {} bool run() { using namespace fmt::internal; @@ -49,7 +49,7 @@ inline std::tm localtime(std::time_t time) { return tm != FMT_NULL; } }; - LocalTime lt(time); + dispatcher lt(time); if (lt.run()) return lt.tm_; // Too big time values may be unsupported. @@ -59,11 +59,11 @@ inline std::tm localtime(std::time_t time) { // Thread-safe replacement for std::gmtime inline std::tm gmtime(std::time_t time) { - struct GMTime { + struct dispatcher { std::time_t time_; std::tm tm_; - GMTime(std::time_t t): time_(t) {} + dispatcher(std::time_t t): time_(t) {} bool run() { using namespace fmt::internal; @@ -85,7 +85,7 @@ inline std::tm gmtime(std::time_t time) { return tm != FMT_NULL; } }; - GMTime gt(time); + dispatcher gt(time); if (gt.run()) return gt.tm_; // Too big time values may be unsupported. @@ -94,11 +94,13 @@ inline std::tm gmtime(std::time_t time) { } namespace internal { -inline std::size_t strftime(char *str, std::size_t count, const char *format, const std::tm *time) { +inline std::size_t strftime(char *str, std::size_t count, const char *format, + const std::tm *time) { return std::strftime(str, count, format, time); } -inline std::size_t strftime(wchar_t *str, std::size_t count, const wchar_t *format, const std::tm *time) { +inline std::size_t strftime(wchar_t *str, std::size_t count, + const wchar_t *format, const std::tm *time) { return std::wcsftime(str, count, format, time); } } @@ -126,7 +128,8 @@ struct formatter { std::size_t start = buf.size(); for (;;) { std::size_t size = buf.capacity() - start; - std::size_t count = internal::strftime(&buf[start], size, &tm_format[0], &tm); + std::size_t count = + internal::strftime(&buf[start], size, &tm_format[0], &tm); if (count != 0) { buf.resize(start + count); break; diff --git a/src/posix.cc b/src/posix.cc index bcdab324..58ec4e60 100644 --- a/src/posix.cc +++ b/src/posix.cc @@ -66,19 +66,19 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; } FMT_BEGIN_NAMESPACE -BufferedFile::~BufferedFile() FMT_NOEXCEPT { +buffered_file::~buffered_file() FMT_NOEXCEPT { if (file_ && FMT_SYSTEM(fclose(file_)) != 0) report_system_error(errno, "cannot close file"); } -BufferedFile::BufferedFile(cstring_view filename, cstring_view mode) { +buffered_file::buffered_file(cstring_view filename, cstring_view mode) { FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), FMT_NULL); if (!file_) FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str())); } -void BufferedFile::close() { +void buffered_file::close() { if (!file_) return; int result = FMT_SYSTEM(fclose(file_)); @@ -90,7 +90,7 @@ void BufferedFile::close() { // A macro used to prevent expansion of fileno on broken versions of MinGW. #define FMT_ARGS -int BufferedFile::fileno() const { +int buffered_file::fileno() const { int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_)); if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor")); @@ -217,13 +217,13 @@ void File::pipe(File &read_end, File &write_end) { write_end = File(fds[1]); } -BufferedFile File::fdopen(const char *mode) { +buffered_file File::fdopen(const char *mode) { // Don't retry as fdopen doesn't return EINTR. FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode)); if (!f) FMT_THROW(system_error(errno, "cannot associate stream with file descriptor")); - BufferedFile file(f); + buffered_file file(f); fd_ = -1; return file; } diff --git a/test/format-test.cc b/test/format-test.cc index d9a20919..daa0b676 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1207,23 +1207,23 @@ TEST(FormatterTest, Examples) { } TEST(FormatIntTest, Data) { - fmt::FormatInt format_int(42); + fmt::format_int format_int(42); EXPECT_EQ("42", std::string(format_int.data(), format_int.size())); } TEST(FormatIntTest, FormatInt) { - EXPECT_EQ("42", fmt::FormatInt(42).str()); - EXPECT_EQ(2u, fmt::FormatInt(42).size()); - EXPECT_EQ("-42", fmt::FormatInt(-42).str()); - EXPECT_EQ(3u, fmt::FormatInt(-42).size()); - EXPECT_EQ("42", fmt::FormatInt(42ul).str()); - EXPECT_EQ("-42", fmt::FormatInt(-42l).str()); - EXPECT_EQ("42", fmt::FormatInt(42ull).str()); - EXPECT_EQ("-42", fmt::FormatInt(-42ll).str()); + EXPECT_EQ("42", fmt::format_int(42).str()); + EXPECT_EQ(2u, fmt::format_int(42).size()); + EXPECT_EQ("-42", fmt::format_int(-42).str()); + EXPECT_EQ(3u, fmt::format_int(-42).size()); + EXPECT_EQ("42", fmt::format_int(42ul).str()); + EXPECT_EQ("-42", fmt::format_int(-42l).str()); + EXPECT_EQ("42", fmt::format_int(42ull).str()); + EXPECT_EQ("-42", fmt::format_int(-42ll).str()); std::ostringstream os; os << std::numeric_limits::max(); EXPECT_EQ(os.str(), - fmt::FormatInt(std::numeric_limits::max()).str()); + fmt::format_int(std::numeric_limits::max()).str()); } template diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 2043f549..b7afb9bc 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -306,7 +306,7 @@ TEST(UtilTest, FormatSystemError) { #if FMT_USE_FILE_DESCRIPTORS -using fmt::BufferedFile; +using fmt::buffered_file; using fmt::ErrorCode; using fmt::File; @@ -319,7 +319,7 @@ TEST(OutputRedirectTest, ScopedRedirect) { File read_end, write_end; File::pipe(read_end, write_end); { - BufferedFile file(write_end.fdopen("w")); + buffered_file file(write_end.fdopen("w")); std::fprintf(file.get(), "[[["); { OutputRedirect redir(file.get()); @@ -336,7 +336,7 @@ TEST(OutputRedirectTest, FlushErrorInCtor) { File::pipe(read_end, write_end); int write_fd = write_end.descriptor(); File write_copy = write_end.dup(write_fd); - BufferedFile f = write_end.fdopen("w"); + buffered_file f = write_end.fdopen("w"); // Put a character in a file buffer. EXPECT_EQ('x', fputc('x', f.get())); FMT_POSIX(close(write_fd)); @@ -348,7 +348,7 @@ TEST(OutputRedirectTest, FlushErrorInCtor) { } TEST(OutputRedirectTest, DupErrorInCtor) { - BufferedFile f = open_buffered_file(); + buffered_file f = open_buffered_file(); int fd = (f.fileno)(); File copy = File::dup(fd); FMT_POSIX(close(fd)); @@ -361,14 +361,14 @@ TEST(OutputRedirectTest, DupErrorInCtor) { TEST(OutputRedirectTest, RestoreAndRead) { File read_end, write_end; File::pipe(read_end, write_end); - BufferedFile file(write_end.fdopen("w")); + buffered_file file(write_end.fdopen("w")); std::fprintf(file.get(), "[[["); OutputRedirect redir(file.get()); std::fprintf(file.get(), "censored"); EXPECT_EQ("censored", sanitize(redir.restore_and_read())); EXPECT_EQ("", sanitize(redir.restore_and_read())); std::fprintf(file.get(), "]]]"); - file = BufferedFile(); + file = buffered_file(); EXPECT_READ(read_end, "[[[]]]"); } @@ -378,7 +378,7 @@ TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) { File::pipe(read_end, write_end); int write_fd = write_end.descriptor(); File write_copy = write_end.dup(write_fd); - BufferedFile f = write_end.fdopen("w"); + buffered_file f = write_end.fdopen("w"); OutputRedirect redir(f.get()); // Put a character in a file buffer. EXPECT_EQ('x', fputc('x', f.get())); @@ -393,7 +393,7 @@ TEST(OutputRedirectTest, ErrorInDtor) { File::pipe(read_end, write_end); int write_fd = write_end.descriptor(); File write_copy = write_end.dup(write_fd); - BufferedFile f = write_end.fdopen("w"); + buffered_file f = write_end.fdopen("w"); scoped_ptr redir(new OutputRedirect(f.get())); // Put a character in a file buffer. EXPECT_EQ('x', fputc('x', f.get())); @@ -405,7 +405,7 @@ TEST(OutputRedirectTest, ErrorInDtor) { FMT_POSIX(close(write_fd)); SUPPRESS_ASSERT(redir.reset()); }, format_system_error(EBADF, "cannot flush stream")); - write_copy.dup2(write_fd); // "undo" close or dtor of BufferedFile will fail + write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail } #endif // FMT_USE_FILE_DESCRIPTORS diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index b724a6ac..607558c8 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -25,7 +25,7 @@ #include "gtest-extra.h" #include "util.h" -using fmt::BufferedFile; +using fmt::buffered_file; using fmt::ErrorCode; using fmt::File; @@ -194,7 +194,7 @@ int (test::fileno)(FILE *stream) { #endif void write_file(fmt::cstring_view filename, fmt::string_view content) { - fmt::BufferedFile f(filename, "w"); + fmt::buffered_file f(filename, "w"); f.print("{}", content); } @@ -383,8 +383,8 @@ TEST(FileTest, FdopenNoRetry) { TEST(BufferedFileTest, OpenRetry) { write_file("test", "there must be something here"); - scoped_ptr f; - EXPECT_RETRY(f.reset(new BufferedFile("test", "r")), + scoped_ptr f; + EXPECT_RETRY(f.reset(new buffered_file("test", "r")), fopen, "cannot open file test"); #ifndef _WIN32 char c = 0; @@ -396,7 +396,7 @@ TEST(BufferedFileTest, OpenRetry) { TEST(BufferedFileTest, CloseNoRetryInDtor) { File read_end, write_end; File::pipe(read_end, write_end); - scoped_ptr f(new BufferedFile(read_end.fdopen("r"))); + scoped_ptr f(new buffered_file(read_end.fdopen("r"))); int saved_fclose_count = 0; EXPECT_WRITE(stderr, { fclose_count = 1; @@ -410,7 +410,7 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) { TEST(BufferedFileTest, CloseNoRetry) { File read_end, write_end; File::pipe(read_end, write_end); - BufferedFile f = read_end.fdopen("r"); + buffered_file f = read_end.fdopen("r"); fclose_count = 1; EXPECT_SYSTEM_ERROR(f.close(), EINTR, "cannot close file"); EXPECT_EQ(2, fclose_count); @@ -420,7 +420,7 @@ TEST(BufferedFileTest, CloseNoRetry) { TEST(BufferedFileTest, FilenoNoRetry) { File read_end, write_end; File::pipe(read_end, write_end); - BufferedFile f = read_end.fdopen("r"); + buffered_file f = read_end.fdopen("r"); fileno_count = 1; EXPECT_SYSTEM_ERROR((f.fileno)(), EINTR, "cannot get file descriptor"); EXPECT_EQ(2, fileno_count); diff --git a/test/posix-test.cc b/test/posix-test.cc index d6b1953c..fd4dcff0 100644 --- a/test/posix-test.cc +++ b/test/posix-test.cc @@ -16,7 +16,7 @@ # undef fileno #endif -using fmt::BufferedFile; +using fmt::buffered_file; using fmt::ErrorCode; using fmt::File; @@ -58,32 +58,32 @@ void write(File &f, fmt::string_view s) { } TEST(BufferedFileTest, DefaultCtor) { - BufferedFile f; + buffered_file f; EXPECT_TRUE(f.get() == 0); } TEST(BufferedFileTest, MoveCtor) { - BufferedFile bf = open_buffered_file(); + buffered_file bf = open_buffered_file(); FILE *fp = bf.get(); EXPECT_TRUE(fp != 0); - BufferedFile bf2(std::move(bf)); + buffered_file bf2(std::move(bf)); EXPECT_EQ(fp, bf2.get()); EXPECT_TRUE(bf.get() == 0); } TEST(BufferedFileTest, MoveAssignment) { - BufferedFile bf = open_buffered_file(); + buffered_file bf = open_buffered_file(); FILE *fp = bf.get(); EXPECT_TRUE(fp != 0); - BufferedFile bf2; + buffered_file bf2; bf2 = std::move(bf); EXPECT_EQ(fp, bf2.get()); EXPECT_TRUE(bf.get() == 0); } TEST(BufferedFileTest, MoveAssignmentClosesFile) { - BufferedFile bf = open_buffered_file(); - BufferedFile bf2 = open_buffered_file(); + buffered_file bf = open_buffered_file(); + buffered_file bf2 = open_buffered_file(); int old_fd = bf2.fileno(); bf2 = std::move(bf); EXPECT_TRUE(isclosed(old_fd)); @@ -91,19 +91,19 @@ TEST(BufferedFileTest, MoveAssignmentClosesFile) { TEST(BufferedFileTest, MoveFromTemporaryInCtor) { FILE *fp = 0; - BufferedFile f(open_buffered_file(&fp)); + buffered_file f(open_buffered_file(&fp)); EXPECT_EQ(fp, f.get()); } TEST(BufferedFileTest, MoveFromTemporaryInAssignment) { FILE *fp = 0; - BufferedFile f; + buffered_file f; f = open_buffered_file(&fp); EXPECT_EQ(fp, f.get()); } TEST(BufferedFileTest, MoveFromTemporaryInAssignmentClosesFile) { - BufferedFile f = open_buffered_file(); + buffered_file f = open_buffered_file(); int old_fd = f.fileno(); f = open_buffered_file(); EXPECT_TRUE(isclosed(old_fd)); @@ -112,14 +112,14 @@ TEST(BufferedFileTest, MoveFromTemporaryInAssignmentClosesFile) { TEST(BufferedFileTest, CloseFileInDtor) { int fd = 0; { - BufferedFile f = open_buffered_file(); + buffered_file f = open_buffered_file(); fd = f.fileno(); } EXPECT_TRUE(isclosed(fd)); } TEST(BufferedFileTest, CloseErrorInDtor) { - scoped_ptr f(new BufferedFile(open_buffered_file())); + scoped_ptr f(new buffered_file(open_buffered_file())); EXPECT_WRITE(stderr, { // The close function must be called inside EXPECT_WRITE, otherwise // the system may recycle closed file descriptor when redirecting the @@ -131,7 +131,7 @@ TEST(BufferedFileTest, CloseErrorInDtor) { } TEST(BufferedFileTest, Close) { - BufferedFile f = open_buffered_file(); + buffered_file f = open_buffered_file(); int fd = f.fileno(); f.close(); EXPECT_TRUE(f.get() == 0); @@ -139,14 +139,14 @@ TEST(BufferedFileTest, Close) { } TEST(BufferedFileTest, CloseError) { - BufferedFile f = open_buffered_file(); + buffered_file f = open_buffered_file(); FMT_POSIX(close(f.fileno())); EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); EXPECT_TRUE(f.get() == 0); } TEST(BufferedFileTest, Fileno) { - BufferedFile f; + buffered_file f; #ifndef __COVERITY__ // fileno on a null FILE pointer either crashes or returns an error. // Disable Coverity because this is intentional. diff --git a/test/util.cc b/test/util.cc index 17837414..51b85e81 100644 --- a/test/util.cc +++ b/test/util.cc @@ -32,12 +32,12 @@ std::string get_system_error(int error_code) { const char *const FILE_CONTENT = "Don't panic!"; -fmt::BufferedFile open_buffered_file(FILE **fp) { +fmt::buffered_file open_buffered_file(FILE **fp) { fmt::File read_end, write_end; fmt::File::pipe(read_end, write_end); write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT)); write_end.close(); - fmt::BufferedFile f = read_end.fdopen("r"); + fmt::buffered_file f = read_end.fdopen("r"); if (fp) *fp = f.get(); return f; diff --git a/test/util.h b/test/util.h index 16aae737..a4440bb0 100644 --- a/test/util.h +++ b/test/util.h @@ -35,7 +35,7 @@ std::string get_system_error(int error_code); extern const char *const FILE_CONTENT; // Opens a buffered file for reading. -fmt::BufferedFile open_buffered_file(FILE **fp = 0); +fmt::buffered_file open_buffered_file(FILE **fp = 0); inline FILE *safe_fopen(const char *filename, const char *mode) { #if defined(_WIN32) && !defined(__MINGW32__) From fbd5153487945fdaebced940c60282b6bc9d7006 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 19 May 2018 10:31:49 -0700 Subject: [PATCH 4/6] Update changelog --- ChangeLog.rst | 61 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index b987c849..0e11e7b1 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -12,7 +12,25 @@ of the library for standardization in `P0645R2 Text Formatting `_. -* Implemented ``constexpr`` parsing of format strings. +* Implemented ``constexpr`` parsing of format strings and `compile-time format + string checks + `_. For + example + + .. code:: c++ + + #include + std::string s = format(fmt("{:d}"), "foo"); + +gives a compile-time error because ``d`` is an invalid specifier for strings +(`godbolt `_):: + + ... + :4:19: note: in instantiation of function template specialization 'fmt::v5::format' requested here + std::string s = format(fmt("{:d}"), "foo"); + ^ + format.h:1337:13: note: non-constexpr function 'on_error' cannot be used in a constant expression + handler.on_error("invalid type specifier"); * Separated format string parsing and formatting in the extension API to enable compile-time format string processing. For example @@ -45,7 +63,7 @@ std::string s = format(fmt("{:x}"), S()); - will give a compile-time error due to invalid format specifier (`godbolt + gives a compile-time error due to invalid format specifier (`godbolt `_):: ... @@ -168,6 +186,10 @@ (`#641 `_). Thanks `@cowo78 (Giuseppe Corbelli) `_. +* Added Gradle build file ``support/build.gradle`` + (`#649 `_). + Thanks `@luncliff (Park DongHa) `_. + * Removed ``FMT_CPPFORMAT`` CMake option. * Fixed a name conflict with the macro ``CHAR_WIDTH`` in glibc @@ -185,9 +207,11 @@ (`#626 `_). Thanks `@aroig (Abdó Roig-Maranges) `_. -* Fixed a warning about unreachable code - (`#640 `_). - Thanks `@peterbell10 `_. +* Fixed various compiler warnings ( + `#640 `_, + `#656 `_). + Thanks `@peterbell10 `_ and + `@LarsGullik `_. * Worked around an MSVC bug and fixed several warnings (`#653 `_). @@ -197,6 +221,33 @@ (`#655 `_). Thanks `@chenxiaolong (Andrew Gunnerson) `_. +* Made ``constexpr remove_prefix`` gcc version check tighter + (`#648 `_). + +* Renamed internal type enum constants to prevent collision with poorly written + C libraries (`#644 `_). + +* Added detection of ``wostream operator<<`` + (`#650 `_). + +* Fixed compilation on OpenBSD + (`#660 `_). + Thanks `@hubslave `_. + +* Improved documentation + (`#661 `_). + Thanks `@johnthagen `_. + +* Fixed compilation when there is a mismatch between ``-std`` options between + the library and user code + (`#664 `_). + +* Improved generated binary code on GCC 7 and older + (`#668 `_). + +* Fixed handling of numeric alignment with no width + (`#675 `_). + 4.1.0 - 2017-12-20 ------------------ From b76bb796133d6902e593ae12c7e3d5d9e50e0cd4 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 19 May 2018 10:32:53 -0700 Subject: [PATCH 5/6] Improve naming consistency --- include/fmt/posix.h | 50 +++++++++++----------- src/posix.cc | 32 +++++++------- test/gtest-extra-test.cc | 36 ++++++++-------- test/gtest-extra.cc | 16 +++---- test/gtest-extra.h | 6 +-- test/posix-mock-test.cc | 66 ++++++++++++++-------------- test/posix-test.cc | 92 ++++++++++++++++++++-------------------- test/printf-test.cc | 4 +- test/util.cc | 4 +- 9 files changed, 153 insertions(+), 153 deletions(-) diff --git a/include/fmt/posix.h b/include/fmt/posix.h index 89aea2a1..8682d716 100644 --- a/include/fmt/posix.h +++ b/include/fmt/posix.h @@ -113,12 +113,12 @@ typedef basic_cstring_view cstring_view; typedef basic_cstring_view wcstring_view; // An error code. -class ErrorCode { +class error_code { private: int value_; public: - explicit ErrorCode(int value = 0) FMT_NOEXCEPT : value_(value) {} + explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} int get() const FMT_NOEXCEPT { return value_; } }; @@ -128,7 +128,7 @@ class buffered_file { private: FILE *file_; - friend class File; + friend class file; explicit buffered_file(FILE *f) : file_(f) {} @@ -222,18 +222,18 @@ public: } }; -// A file. Closed file is represented by a File object with descriptor -1. +// A file. Closed file is represented by a file object with descriptor -1. // Methods that are not declared with FMT_NOEXCEPT may throw // fmt::system_error in case of failure. Note that some errors such as // closing the file multiple times will cause a crash on Windows rather // than an exception. You can get standard behavior by overriding the // invalid parameter handler with _set_invalid_parameter_handler. -class File { +class file { private: int fd_; // File descriptor. - // Constructs a File object with a given descriptor. - explicit File(int fd) : fd_(fd) {} + // Constructs a file object with a given descriptor. + explicit file(int fd) : fd_(fd) {} public: // Possible values for the oflag argument to the constructor. @@ -243,11 +243,11 @@ class File { RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. }; - // Constructs a File object which doesn't represent any file. - File() FMT_NOEXCEPT : fd_(-1) {} + // Constructs a file object which doesn't represent any file. + file() FMT_NOEXCEPT : fd_(-1) {} - // Opens a file and constructs a File object representing this file. - FMT_API File(cstring_view path, int oflag); + // Opens a file and constructs a file object representing this file. + FMT_API file(cstring_view path, int oflag); #if !FMT_USE_RVALUE_REFERENCES // Emulate a move constructor and a move assignment operator if rvalue @@ -262,22 +262,22 @@ class File { public: // A "move constructor" for moving from a temporary. - File(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {} + file(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {} // A "move constructor" for moving from an lvalue. - File(File &other) FMT_NOEXCEPT : fd_(other.fd_) { + file(file &other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; } // A "move assignment operator" for moving from a temporary. - File &operator=(Proxy p) { + file &operator=(Proxy p) { close(); fd_ = p.fd; return *this; } // A "move assignment operator" for moving from an lvalue. - File &operator=(File &other) { + file &operator=(file &other) { close(); fd_ = other.fd_; other.fd_ = -1; @@ -285,7 +285,7 @@ class File { } // Returns a proxy object for moving from a temporary: - // File file = File(...); + // file f = file(...); operator Proxy() FMT_NOEXCEPT { Proxy p = {fd_}; fd_ = -1; @@ -294,14 +294,14 @@ class File { #else private: - FMT_DISALLOW_COPY_AND_ASSIGN(File); + FMT_DISALLOW_COPY_AND_ASSIGN(file); public: - File(File &&other) FMT_NOEXCEPT : fd_(other.fd_) { + file(file &&other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; } - File& operator=(File &&other) { + file& operator=(file &&other) { close(); fd_ = other.fd_; other.fd_ = -1; @@ -310,7 +310,7 @@ class File { #endif // Destroys the object closing the file it represents if any. - FMT_API ~File() FMT_DTOR_NOEXCEPT; + FMT_API ~file() FMT_DTOR_NOEXCEPT; // Returns the file descriptor. int descriptor() const FMT_NOEXCEPT { return fd_; } @@ -330,7 +330,7 @@ class File { // Duplicates a file descriptor with the dup function and returns // the duplicate as a file object. - FMT_API static File dup(int fd); + FMT_API static file dup(int fd); // Makes fd be the copy of this file descriptor, closing fd first if // necessary. @@ -338,14 +338,14 @@ class File { // Makes fd be the copy of this file descriptor, closing fd first if // necessary. - FMT_API void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT; + FMT_API void dup2(int fd, error_code &ec) FMT_NOEXCEPT; // Creates a pipe setting up read_end and write_end file objects for reading // and writing respectively. - FMT_API static void pipe(File &read_end, File &write_end); + FMT_API static void pipe(file &read_end, file &write_end); // Creates a buffered_file object associated with this file and detaches - // this File object from the file. + // this file object from the file. FMT_API buffered_file fdopen(const char *mode); }; @@ -410,7 +410,7 @@ FMT_END_NAMESPACE namespace std { // For compatibility with C++98. inline fmt::buffered_file &move(fmt::buffered_file &f) { return f; } -inline fmt::File &move(fmt::File &f) { return f; } +inline fmt::file &move(fmt::file &f) { return f; } } #endif diff --git a/src/posix.cc b/src/posix.cc index 58ec4e60..d672b0fc 100644 --- a/src/posix.cc +++ b/src/posix.cc @@ -97,7 +97,7 @@ int buffered_file::fileno() const { return fd; } -File::File(cstring_view path, int oflag) { +file::file(cstring_view path, int oflag) { int mode = S_IRUSR | S_IWUSR; #if defined(_WIN32) && !defined(__MINGW32__) fd_ = -1; @@ -109,14 +109,14 @@ File::File(cstring_view path, int oflag) { FMT_THROW(system_error(errno, "cannot open file {}", path.c_str())); } -File::~File() FMT_NOEXCEPT { +file::~file() FMT_NOEXCEPT { // Don't retry close in case of EINTR! // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0) report_system_error(errno, "cannot close file"); } -void File::close() { +void file::close() { if (fd_ == -1) return; // Don't retry close in case of EINTR! @@ -127,7 +127,7 @@ void File::close() { FMT_THROW(system_error(errno, "cannot close file")); } -long long File::size() const { +long long file::size() const { #ifdef _WIN32 // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT // is less than 0x0500 as is the case with some default MinGW builds. @@ -148,12 +148,12 @@ long long File::size() const { if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) FMT_THROW(system_error(errno, "cannot get file attributes")); static_assert(sizeof(long long) >= sizeof(file_stat.st_size), - "return type of File::size is not large enough"); + "return type of file::size is not large enough"); return file_stat.st_size; #endif } -std::size_t File::read(void *buffer, std::size_t count) { +std::size_t file::read(void *buffer, std::size_t count) { RWResult result = 0; FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); if (result < 0) @@ -161,7 +161,7 @@ std::size_t File::read(void *buffer, std::size_t count) { return internal::to_unsigned(result); } -std::size_t File::write(const void *buffer, std::size_t count) { +std::size_t file::write(const void *buffer, std::size_t count) { RWResult result = 0; FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); if (result < 0) @@ -169,16 +169,16 @@ std::size_t File::write(const void *buffer, std::size_t count) { return internal::to_unsigned(result); } -File File::dup(int fd) { +file file::dup(int fd) { // Don't retry as dup doesn't return EINTR. // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html int new_fd = FMT_POSIX_CALL(dup(fd)); if (new_fd == -1) FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd)); - return File(new_fd); + return file(new_fd); } -void File::dup2(int fd) { +void file::dup2(int fd) { int result = 0; FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); if (result == -1) { @@ -187,14 +187,14 @@ void File::dup2(int fd) { } } -void File::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT { +void file::dup2(int fd, error_code &ec) FMT_NOEXCEPT { int result = 0; FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); if (result == -1) - ec = ErrorCode(errno); + ec = error_code(errno); } -void File::pipe(File &read_end, File &write_end) { +void file::pipe(file &read_end, file &write_end) { // Close the descriptors first to make sure that assignments don't throw // and there are no leaks. read_end.close(); @@ -213,11 +213,11 @@ void File::pipe(File &read_end, File &write_end) { FMT_THROW(system_error(errno, "cannot create pipe")); // The following assignments don't throw because read_fd and write_fd // are closed. - read_end = File(fds[0]); - write_end = File(fds[1]); + read_end = file(fds[0]); + write_end = file(fds[1]); } -buffered_file File::fdopen(const char *mode) { +buffered_file file::fdopen(const char *mode) { // Don't retry as fdopen doesn't return EINTR. FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode)); if (!f) diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index b7afb9bc..8e42d8e2 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -307,17 +307,17 @@ TEST(UtilTest, FormatSystemError) { #if FMT_USE_FILE_DESCRIPTORS using fmt::buffered_file; -using fmt::ErrorCode; -using fmt::File; +using fmt::error_code; +using fmt::file; TEST(ErrorCodeTest, Ctor) { - EXPECT_EQ(0, ErrorCode().get()); - EXPECT_EQ(42, ErrorCode(42).get()); + EXPECT_EQ(0, error_code().get()); + EXPECT_EQ(42, error_code(42).get()); } TEST(OutputRedirectTest, ScopedRedirect) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); { buffered_file file(write_end.fdopen("w")); std::fprintf(file.get(), "[[["); @@ -332,10 +332,10 @@ TEST(OutputRedirectTest, ScopedRedirect) { // Test that OutputRedirect handles errors in flush correctly. TEST(OutputRedirectTest, FlushErrorInCtor) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); int write_fd = write_end.descriptor(); - File write_copy = write_end.dup(write_fd); + file write_copy = write_end.dup(write_fd); buffered_file f = write_end.fdopen("w"); // Put a character in a file buffer. EXPECT_EQ('x', fputc('x', f.get())); @@ -350,7 +350,7 @@ TEST(OutputRedirectTest, FlushErrorInCtor) { TEST(OutputRedirectTest, DupErrorInCtor) { buffered_file f = open_buffered_file(); int fd = (f.fileno)(); - File copy = File::dup(fd); + file copy = file::dup(fd); FMT_POSIX(close(fd)); scoped_ptr redir; EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new OutputRedirect(f.get())), @@ -359,8 +359,8 @@ TEST(OutputRedirectTest, DupErrorInCtor) { } TEST(OutputRedirectTest, RestoreAndRead) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); buffered_file file(write_end.fdopen("w")); std::fprintf(file.get(), "[[["); OutputRedirect redir(file.get()); @@ -374,10 +374,10 @@ TEST(OutputRedirectTest, RestoreAndRead) { // Test that OutputRedirect handles errors in flush correctly. TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); int write_fd = write_end.descriptor(); - File write_copy = write_end.dup(write_fd); + file write_copy = write_end.dup(write_fd); buffered_file f = write_end.fdopen("w"); OutputRedirect redir(f.get()); // Put a character in a file buffer. @@ -389,10 +389,10 @@ TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) { } TEST(OutputRedirectTest, ErrorInDtor) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); int write_fd = write_end.descriptor(); - File write_copy = write_end.dup(write_fd); + file write_copy = write_end.dup(write_fd); buffered_file f = write_end.fdopen("w"); scoped_ptr redir(new OutputRedirect(f.get())); // Put a character in a file buffer. diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc index 21a7ea97..2c4c34a7 100644 --- a/test/gtest-extra.cc +++ b/test/gtest-extra.cc @@ -9,7 +9,7 @@ #if FMT_USE_FILE_DESCRIPTORS -using fmt::File; +using fmt::file; void OutputRedirect::flush() { #if EOF != -1 @@ -30,14 +30,14 @@ void OutputRedirect::restore() { original_.close(); } -OutputRedirect::OutputRedirect(FILE *file) : file_(file) { +OutputRedirect::OutputRedirect(FILE *f) : file_(f) { flush(); - int fd = FMT_POSIX(fileno(file)); - // Create a File object referring to the original file. - original_ = File::dup(fd); + int fd = FMT_POSIX(fileno(f)); + // Create a file object referring to the original file. + original_ = file::dup(fd); // Create a pipe. - File write_end; - File::pipe(read_end_, write_end); + file write_end; + file::pipe(read_end_, write_end); // Connect the passed FILE object to the write end of the pipe. write_end.dup2(fd); } @@ -69,7 +69,7 @@ std::string OutputRedirect::restore_and_read() { return content; } -std::string read(File &f, std::size_t count) { +std::string read(file &f, std::size_t count) { std::string buffer(count, '\0'); std::size_t n = 0, offset = 0; do { diff --git a/test/gtest-extra.h b/test/gtest-extra.h index 198d9b1e..db0323d2 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -74,8 +74,8 @@ std::string format_system_error(int error_code, fmt::string_view message); class OutputRedirect { private: FILE *file_; - fmt::File original_; // Original file passed to redirector. - fmt::File read_end_; // Read end of the pipe where the output is redirected. + fmt::file original_; // Original file passed to redirector. + fmt::file read_end_; // Read end of the pipe where the output is redirected. GTEST_DISALLOW_COPY_AND_ASSIGN_(OutputRedirect); @@ -145,7 +145,7 @@ class SuppressAssert { EXPECT_SYSTEM_ERROR(SUPPRESS_ASSERT(statement), error_code, message) // Attempts to read count characters from a file. -std::string read(fmt::File &f, std::size_t count); +std::string read(fmt::file &f, std::size_t count); #define EXPECT_READ(file, expected_content) \ EXPECT_EQ(expected_content, read(file, std::strlen(expected_content))) diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index 607558c8..f69313c3 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -26,8 +26,8 @@ #include "util.h" using fmt::buffered_file; -using fmt::ErrorCode; -using fmt::File; +using fmt::error_code; +using fmt::file; using testing::internal::scoped_ptr; using testing::_; @@ -214,8 +214,8 @@ TEST(UtilTest, GetPageSize) { TEST(FileTest, OpenRetry) { write_file("test", "there must be something here"); - scoped_ptr f; - EXPECT_RETRY(f.reset(new File("test", File::RDONLY)), + scoped_ptr f; + EXPECT_RETRY(f.reset(new file("test", file::RDONLY)), open, "cannot open file test"); #ifndef _WIN32 char c = 0; @@ -224,9 +224,9 @@ TEST(FileTest, OpenRetry) { } TEST(FileTest, CloseNoRetryInDtor) { - File read_end, write_end; - File::pipe(read_end, write_end); - scoped_ptr f(new File(std::move(read_end))); + file read_end, write_end; + file::pipe(read_end, write_end); + scoped_ptr f(new file(std::move(read_end))); int saved_close_count = 0; EXPECT_WRITE(stderr, { close_count = 1; @@ -238,8 +238,8 @@ TEST(FileTest, CloseNoRetryInDtor) { } TEST(FileTest, CloseNoRetry) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); close_count = 1; EXPECT_SYSTEM_ERROR(read_end.close(), EINTR, "cannot close file"); EXPECT_EQ(2, close_count); @@ -249,7 +249,7 @@ TEST(FileTest, CloseNoRetry) { TEST(FileTest, Size) { std::string content = "top secret, destroy before reading"; write_file("test", content); - File f("test", File::RDONLY); + file f("test", file::RDONLY); EXPECT_GE(f.size(), 0); EXPECT_EQ(content.size(), static_cast(f.size())); #ifdef _WIN32 @@ -267,7 +267,7 @@ TEST(FileTest, Size) { TEST(FileTest, MaxSize) { write_file("test", ""); - File f("test", File::RDONLY); + file f("test", file::RDONLY); fstat_sim = MAX_SIZE; EXPECT_GE(f.size(), 0); EXPECT_EQ(max_file_size(), f.size()); @@ -275,8 +275,8 @@ TEST(FileTest, MaxSize) { } TEST(FileTest, ReadRetry) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); enum { SIZE = 4 }; write_end.write("test", SIZE); write_end.close(); @@ -288,8 +288,8 @@ TEST(FileTest, ReadRetry) { } TEST(FileTest, WriteRetry) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); enum { SIZE = 4 }; std::size_t count = 0; EXPECT_RETRY(count = write_end.write("test", SIZE), @@ -306,8 +306,8 @@ TEST(FileTest, WriteRetry) { #ifdef _WIN32 TEST(FileTest, ConvertReadCount) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); char c; std::size_t size = UINT_MAX; if (sizeof(unsigned) != sizeof(std::size_t)) @@ -320,8 +320,8 @@ TEST(FileTest, ConvertReadCount) { } TEST(FileTest, ConvertWriteCount) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); char c; std::size_t size = UINT_MAX; if (sizeof(unsigned) != sizeof(std::size_t)) @@ -337,14 +337,14 @@ TEST(FileTest, ConvertWriteCount) { TEST(FileTest, DupNoRetry) { int stdout_fd = FMT_POSIX(fileno(stdout)); dup_count = 1; - EXPECT_SYSTEM_ERROR(File::dup(stdout_fd), EINTR, + EXPECT_SYSTEM_ERROR(file::dup(stdout_fd), EINTR, fmt::format("cannot duplicate file descriptor {}", stdout_fd)); dup_count = 0; } TEST(FileTest, Dup2Retry) { int stdout_fd = FMT_POSIX(fileno(stdout)); - File f1 = File::dup(stdout_fd), f2 = File::dup(stdout_fd); + file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd); EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2, fmt::format("cannot duplicate file descriptor {} to {}", f1.descriptor(), f2.descriptor())); @@ -352,8 +352,8 @@ TEST(FileTest, Dup2Retry) { TEST(FileTest, Dup2NoExceptRetry) { int stdout_fd = FMT_POSIX(fileno(stdout)); - File f1 = File::dup(stdout_fd), f2 = File::dup(stdout_fd); - ErrorCode ec; + file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd); + error_code ec; dup2_count = 1; f1.dup2(f2.descriptor(), ec); #ifndef _WIN32 @@ -365,16 +365,16 @@ TEST(FileTest, Dup2NoExceptRetry) { } TEST(FileTest, PipeNoRetry) { - File read_end, write_end; + file read_end, write_end; pipe_count = 1; EXPECT_SYSTEM_ERROR( - File::pipe(read_end, write_end), EINTR, "cannot create pipe"); + file::pipe(read_end, write_end), EINTR, "cannot create pipe"); pipe_count = 0; } TEST(FileTest, FdopenNoRetry) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); fdopen_count = 1; EXPECT_SYSTEM_ERROR(read_end.fdopen("r"), EINTR, "cannot associate stream with file descriptor"); @@ -394,8 +394,8 @@ TEST(BufferedFileTest, OpenRetry) { } TEST(BufferedFileTest, CloseNoRetryInDtor) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); scoped_ptr f(new buffered_file(read_end.fdopen("r"))); int saved_fclose_count = 0; EXPECT_WRITE(stderr, { @@ -408,8 +408,8 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) { } TEST(BufferedFileTest, CloseNoRetry) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); buffered_file f = read_end.fdopen("r"); fclose_count = 1; EXPECT_SYSTEM_ERROR(f.close(), EINTR, "cannot close file"); @@ -418,8 +418,8 @@ TEST(BufferedFileTest, CloseNoRetry) { } TEST(BufferedFileTest, FilenoNoRetry) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); buffered_file f = read_end.fdopen("r"); fileno_count = 1; EXPECT_SYSTEM_ERROR((f.fileno)(), EINTR, "cannot get file descriptor"); diff --git a/test/posix-test.cc b/test/posix-test.cc index fd4dcff0..aa0f0a50 100644 --- a/test/posix-test.cc +++ b/test/posix-test.cc @@ -17,8 +17,8 @@ #endif using fmt::buffered_file; -using fmt::ErrorCode; -using fmt::File; +using fmt::error_code; +using fmt::file; using testing::internal::scoped_ptr; @@ -36,16 +36,16 @@ bool isclosed(int fd) { } // Opens a file for reading. -File open_file() { - File read_end, write_end; - File::pipe(read_end, write_end); +file open_file() { + file read_end, write_end; + file::pipe(read_end, write_end); write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT)); write_end.close(); return read_end; } // Attempts to write a string to a file. -void write(File &f, fmt::string_view s) { +void write(file &f, fmt::string_view s) { std::size_t num_chars_left = s.size(); const char *ptr = s.data(); do { @@ -160,12 +160,12 @@ TEST(BufferedFileTest, Fileno) { #endif f = open_buffered_file(); EXPECT_TRUE(f.fileno() != -1); - File copy = File::dup(f.fileno()); + file copy = file::dup(f.fileno()); EXPECT_READ(copy, FILE_CONTENT); } TEST(FileTest, DefaultCtor) { - File f; + file f; EXPECT_EQ(-1, f.descriptor()); } @@ -173,64 +173,64 @@ TEST(FileTest, OpenBufferedFileInCtor) { FILE *fp = safe_fopen("test-file", "w"); std::fputs(FILE_CONTENT, fp); std::fclose(fp); - File f("test-file", File::RDONLY); + file f("test-file", file::RDONLY); ASSERT_TRUE(isopen(f.descriptor())); } TEST(FileTest, OpenBufferedFileError) { - EXPECT_SYSTEM_ERROR(File("nonexistent", File::RDONLY), + EXPECT_SYSTEM_ERROR(file("nonexistent", file::RDONLY), ENOENT, "cannot open file nonexistent"); } TEST(FileTest, MoveCtor) { - File f = open_file(); + file f = open_file(); int fd = f.descriptor(); EXPECT_NE(-1, fd); - File f2(std::move(f)); + file f2(std::move(f)); EXPECT_EQ(fd, f2.descriptor()); EXPECT_EQ(-1, f.descriptor()); } TEST(FileTest, MoveAssignment) { - File f = open_file(); + file f = open_file(); int fd = f.descriptor(); EXPECT_NE(-1, fd); - File f2; + file f2; f2 = std::move(f); EXPECT_EQ(fd, f2.descriptor()); EXPECT_EQ(-1, f.descriptor()); } TEST(FileTest, MoveAssignmentClosesFile) { - File f = open_file(); - File f2 = open_file(); + file f = open_file(); + file f2 = open_file(); int old_fd = f2.descriptor(); f2 = std::move(f); EXPECT_TRUE(isclosed(old_fd)); } -File OpenBufferedFile(int &fd) { - File f = open_file(); +file OpenBufferedFile(int &fd) { + file f = open_file(); fd = f.descriptor(); return f; } TEST(FileTest, MoveFromTemporaryInCtor) { int fd = 0xdead; - File f(OpenBufferedFile(fd)); + file f(OpenBufferedFile(fd)); EXPECT_EQ(fd, f.descriptor()); } TEST(FileTest, MoveFromTemporaryInAssignment) { int fd = 0xdead; - File f; + file f; f = OpenBufferedFile(fd); EXPECT_EQ(fd, f.descriptor()); } TEST(FileTest, MoveFromTemporaryInAssignmentClosesFile) { int fd = 0xdead; - File f = open_file(); + file f = open_file(); int old_fd = f.descriptor(); f = OpenBufferedFile(fd); EXPECT_TRUE(isclosed(old_fd)); @@ -239,14 +239,14 @@ TEST(FileTest, MoveFromTemporaryInAssignmentClosesFile) { TEST(FileTest, CloseFileInDtor) { int fd = 0; { - File f = open_file(); + file f = open_file(); fd = f.descriptor(); } EXPECT_TRUE(isclosed(fd)); } TEST(FileTest, CloseErrorInDtor) { - scoped_ptr f(new File(open_file())); + scoped_ptr f(new file(open_file())); EXPECT_WRITE(stderr, { // The close function must be called inside EXPECT_WRITE, otherwise // the system may recycle closed file descriptor when redirecting the @@ -258,7 +258,7 @@ TEST(FileTest, CloseErrorInDtor) { } TEST(FileTest, Close) { - File f = open_file(); + file f = open_file(); int fd = f.descriptor(); f.close(); EXPECT_EQ(-1, f.descriptor()); @@ -266,19 +266,19 @@ TEST(FileTest, Close) { } TEST(FileTest, CloseError) { - File f = open_file(); + file f = open_file(); FMT_POSIX(close(f.descriptor())); EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); EXPECT_EQ(-1, f.descriptor()); } TEST(FileTest, Read) { - File f = open_file(); + file f = open_file(); EXPECT_READ(f, FILE_CONTENT); } TEST(FileTest, ReadError) { - File f("test-file", File::WRONLY); + file f("test-file", file::WRONLY); char buf; // We intentionally read from a file opened in the write-only mode to // cause error. @@ -286,23 +286,23 @@ TEST(FileTest, ReadError) { } TEST(FileTest, Write) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); write(write_end, "test"); write_end.close(); EXPECT_READ(read_end, "test"); } TEST(FileTest, WriteError) { - File f("test-file", File::RDONLY); + file f("test-file", file::RDONLY); // We intentionally write to a file opened in the read-only mode to // cause error. EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file"); } TEST(FileTest, Dup) { - File f = open_file(); - File copy = File::dup(f.descriptor()); + file f = open_file(); + file copy = file::dup(f.descriptor()); EXPECT_NE(f.descriptor(), copy.descriptor()); EXPECT_EQ(FILE_CONTENT, read(copy, std::strlen(FILE_CONTENT))); } @@ -310,29 +310,29 @@ TEST(FileTest, Dup) { #ifndef __COVERITY__ TEST(FileTest, DupError) { int value = -1; - EXPECT_SYSTEM_ERROR_NOASSERT(File::dup(value), + EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value), EBADF, "cannot duplicate file descriptor -1"); } #endif TEST(FileTest, Dup2) { - File f = open_file(); - File copy = open_file(); + file f = open_file(); + file copy = open_file(); f.dup2(copy.descriptor()); EXPECT_NE(f.descriptor(), copy.descriptor()); EXPECT_READ(copy, FILE_CONTENT); } TEST(FileTest, Dup2Error) { - File f = open_file(); + file f = open_file(); EXPECT_SYSTEM_ERROR_NOASSERT(f.dup2(-1), EBADF, fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor())); } TEST(FileTest, Dup2NoExcept) { - File f = open_file(); - File copy = open_file(); - ErrorCode ec; + file f = open_file(); + file copy = open_file(); + error_code ec; f.dup2(copy.descriptor(), ec); EXPECT_EQ(0, ec.get()); EXPECT_NE(f.descriptor(), copy.descriptor()); @@ -340,15 +340,15 @@ TEST(FileTest, Dup2NoExcept) { } TEST(FileTest, Dup2NoExceptError) { - File f = open_file(); - ErrorCode ec; + file f = open_file(); + error_code ec; SUPPRESS_ASSERT(f.dup2(-1, ec)); EXPECT_EQ(EBADF, ec.get()); } TEST(FileTest, Pipe) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); EXPECT_NE(-1, read_end.descriptor()); EXPECT_NE(-1, write_end.descriptor()); write(write_end, "test"); @@ -356,14 +356,14 @@ TEST(FileTest, Pipe) { } TEST(FileTest, Fdopen) { - File read_end, write_end; - File::pipe(read_end, write_end); + file read_end, write_end; + file::pipe(read_end, write_end); int read_fd = read_end.descriptor(); EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get()))); } TEST(FileTest, FdopenError) { - File f; + file f; EXPECT_SYSTEM_ERROR_NOASSERT( f.fdopen("r"), EBADF, "cannot associate stream with file descriptor"); } diff --git a/test/printf-test.cc b/test/printf-test.cc index 3f877898..c15a3201 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -478,8 +478,8 @@ TEST(PrintfTest, Examples) { } TEST(PrintfTest, PrintfError) { - fmt::File read_end, write_end; - fmt::File::pipe(read_end, write_end); + fmt::file read_end, write_end; + fmt::file::pipe(read_end, write_end); int result = fmt::fprintf(read_end.fdopen("r").get(), "test"); EXPECT_LT(result, 0); } diff --git a/test/util.cc b/test/util.cc index 51b85e81..12270bcc 100644 --- a/test/util.cc +++ b/test/util.cc @@ -33,8 +33,8 @@ std::string get_system_error(int error_code) { const char *const FILE_CONTENT = "Don't panic!"; fmt::buffered_file open_buffered_file(FILE **fp) { - fmt::File read_end, write_end; - fmt::File::pipe(read_end, write_end); + fmt::file read_end, write_end; + fmt::file::pipe(read_end, write_end); write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT)); write_end.close(); fmt::buffered_file f = read_end.fdopen("r"); From 3e3a27740e6ba4360b5b64a0fc7adfb5279c596a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 19 May 2018 11:46:55 -0700 Subject: [PATCH 6/6] Update changelog --- ChangeLog.rst | 80 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 0e11e7b1..e27e3d82 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -22,8 +22,8 @@ #include std::string s = format(fmt("{:d}"), "foo"); -gives a compile-time error because ``d`` is an invalid specifier for strings -(`godbolt `_):: + gives a compile-time error because ``d`` is an invalid specifier for strings + (`godbolt `_):: ... :4:19: note: in instantiation of function template specialization 'fmt::v5::format' requested here @@ -32,6 +32,9 @@ gives a compile-time error because ``d`` is an invalid specifier for strings format.h:1337:13: note: non-constexpr function 'on_error' cannot be used in a constant expression handler.on_error("invalid type specifier"); + Compile-time checks require relaxed ``constexpr`` (C++14 feature) support. If + the latter is not available, checks will be performed at runtime. + * Separated format string parsing and formatting in the extension API to enable compile-time format string processing. For example @@ -70,9 +73,6 @@ gives a compile-time error because ``d`` is an invalid specifier for strings :12:45: error: expression '' is not a constant expression throw format_error("invalid specifier"); - Compile-time checks require relaxed ``constexpr`` (C++14 feature) support. If - the latter is not available, checks will be performed at runtime. - * Added `iterator support `_: @@ -84,6 +84,17 @@ gives a compile-time error because ``d`` is an invalid specifier for strings std::vector out; fmt::format_to(std::back_inserter(out), "{}", 42); +* Added the `format_to_n + `_ + function that restricts the output to the specified number of characters + (`#298 `_): + + .. code:: c++ + + char out[4]; + fmt::format_to_n(out, sizeof(out), "{}", 12345); + // out == "1234" (without terminating '\0') + * Added the `formatted_size `_ function for computing the output size: @@ -122,6 +133,9 @@ gives a compile-time error because ``d`` is an invalid specifier for strings vreport_error(format, fmt::make_format_args(args...)); } +* Added the ``make_printf_args`` function for capturing ``printf`` arguments. + Thanks `@Kronuz (Germán Méndez Bravo) `_. + * Added prefix ``v`` to non-variadic functions taking ``format_args`` to distinguish them from variadic ones: @@ -162,16 +176,15 @@ gives a compile-time error because ``d`` is an invalid specifier for strings * Disallowed formatting of multibyte strings into a wide character target (`#606 `_). -* Added a section describing `the use of header-only target with CMake - `_ to the docs - (`#515 `_). - Thanks `@ibell (Ian Bell) `_. - -* Added a `note about errno `_ to the - documentation ( +* Improved documentation ( + `#515 `_, `#614 `_, - `#617 `_). - Thanks `@mihaitodor (Mihai Todor) `_. + `#617 `_, + `#661 `_, + `#680 `_). + Thanks `@ibell (Ian Bell) `_, + `@mihaitodor (Mihai Todor) `_, and + `@johnthagen `_. * Implemented more efficient handling of large number of format arguments. @@ -182,6 +195,10 @@ gives a compile-time error because ``d`` is an invalid specifier for strings (`#397 `_). Thanks `@chronoxor (Ivan Shynkarenka) `_. +* Moved ``fmt/*.h`` to ``include/fmt/*.h`` to prevent irrelevant files and + directories appearing on the include search paths when fmt is used as a + subproject and moved source files to the ``src`` directory. + * Added qmake project file ``support/fmt.pro`` (`#641 `_). Thanks `@cowo78 (Giuseppe Corbelli) `_. @@ -209,14 +226,23 @@ gives a compile-time error because ``d`` is an invalid specifier for strings * Fixed various compiler warnings ( `#640 `_, - `#656 `_). - Thanks `@peterbell10 `_ and - `@LarsGullik `_. + `#656 `_, + `#679 `_, + `#681 `_, + `#705 `_). + Thanks `@peterbell10 `_, + `@LarsGullik `_, + `@foonathan (Jonathan Müller) `_, + `@eliaskosunen (Elias Kosunen) `_, and + `@christianparpart (Christian Parpart) `_. * Worked around an MSVC bug and fixed several warnings (`#653 `_). Thanks `@alabuzhev (Alex Alabuzhev) `_. +* Worked around GCC bug 67371 + (`#682 `_). + * Fixed compilation with ``-fno-exceptions`` (`#655 `_). Thanks `@chenxiaolong (Andrew Gunnerson) `_. @@ -234,10 +260,6 @@ gives a compile-time error because ``d`` is an invalid specifier for strings (`#660 `_). Thanks `@hubslave `_. -* Improved documentation - (`#661 `_). - Thanks `@johnthagen `_. - * Fixed compilation when there is a mismatch between ``-std`` options between the library and user code (`#664 `_). @@ -248,6 +270,22 @@ gives a compile-time error because ``d`` is an invalid specifier for strings * Fixed handling of numeric alignment with no width (`#675 `_). +* Fixed handling of empty strings in UTF8/16 converters + (`#676 `_). + Thanks `@vgalka-sl (Vasili Galka) `_. + +* Fixed formatting of an empty ``string_view`` + (`#689 `_). + +* Fixed detection of ``string_view`` on libc++ + (`#686 `_). + +* Fixed DLL issues (`#696 `_). + Thanks `@sebkoenig `_. + +* Fixed compile checks for mixing narrow and wide strings + (`#690 `_). + 4.1.0 - 2017-12-20 ------------------