diff --git a/include/fmt/iterator.h b/include/fmt/iterator.h new file mode 100644 index 00000000..2d05e7c7 --- /dev/null +++ b/include/fmt/iterator.h @@ -0,0 +1,81 @@ +// Copyright (c) Google LLC. +// SPDX-License-Identifier: MIT Licence +// + +#ifndef FMT_ITERATOR_H_ +#define FMT_ITERATOR_H_ + +#include + +#include "core.h" +#include "format.h" + +FMT_BEGIN_NAMESPACE + +namespace internal { +#if defined(__cpp_concepts) and __cpp_concepts >= 201907 +template concept formattable = requires(T&& t) { + ::fmt::format("{}", std::forward(t)); +}; +#endif // __cpp_concepts +} // namespace internal + +// Iterator that allows for writing to file via fmt::print. +// +template class basic_print_iterator { + public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + using char_type = CharT; + + basic_print_iterator() = default; + + // Constructs a print-iterator that writes to stdout. + explicit basic_print_iterator(basic_string_view const format_specifier) + : file_(stdout), format_specifier_(format_specifier) {} + + // Constructs a print-iterator that writes to a designated file. + explicit basic_print_iterator( + std::FILE* file, basic_string_view const format_specifier = "{}") + : file_(file), format_specifier_(format_specifier) {} + + basic_print_iterator& operator++() noexcept { return *this; } + basic_print_iterator& operator++(int) noexcept { return *this; } + basic_print_iterator& operator*() noexcept { return *this; } + +#if defined(__cpp_concepts) and __cpp_concepts >= 201907 + template +#else + template +#endif // __cpp_concepts + void operator=(T&& t) + { + ::fmt::print(file_, format_specifier_, std::forward(t)); + } + + private: + std::FILE* file_ = nullptr; + basic_string_view format_specifier_; +}; + +#if __cplusplus >= 201703 +template +basic_print_iterator(basic_string_view) -> basic_print_iterator; + +template +basic_print_iterator(std::FILE*, basic_string_view) + -> basic_print_iterator; +#endif // __cplusplus + +using print_iterator = basic_print_iterator; +using wprint_iterator = basic_print_iterator; +using u8print_iterator = basic_print_iterator; +using u16print_iterator = basic_print_iterator; +using u32print_iterator = basic_print_iterator; + +FMT_END_NAMESPACE + +#endif // FMT_ITERATOR_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ad391c22..5f532ab5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,7 +24,7 @@ endif () if (MSVC) # Workaround a bug in implementation of variadic templates in MSVC11. target_compile_definitions(gmock PUBLIC _VARIADIC_MAX=10) - + # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. target_compile_definitions(gmock PRIVATE _CRT_SECURE_NO_WARNINGS) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -103,6 +103,7 @@ endif () if (NOT (MSVC AND BUILD_SHARED_LIBS)) add_fmt_test(format-impl-test) endif () +add_fmt_test(iterator-test) add_fmt_test(locale-test) add_fmt_test(ostream-test) add_fmt_test(compile-test) diff --git a/test/iterator-test.cc b/test/iterator-test.cc new file mode 100644 index 00000000..6a53ca60 --- /dev/null +++ b/test/iterator-test.cc @@ -0,0 +1,102 @@ +// Copyright (c) Google LLC. +// SPDX-License-Identifier: MIT Licence +// +#include "fmt/iterator.h" + +#include +#include +#include +#include + +#include "fmt/ranges.h" +#include "gtest-extra.h" +#include "gtest/gtest.h" + +namespace { +#if defined(__cpp_lib_ranges) and __cpp_lib_ranges >= 201911 +template void check_iterator_conformance() { + static_assert(not std::readable); + static_assert(std::output_iterator); + static_assert(std::output_iterator); + static_assert(std::output_iterator); + static_assert(not std::output_iterator>); +} + +template [[maybe_unused]] void +check_iterator_conformance(); +template [[maybe_unused]] void +check_iterator_conformance(); +template [[maybe_unused]] void +check_iterator_conformance(); +template [[maybe_unused]] void +check_iterator_conformance(); +template [[maybe_unused]] void +check_iterator_conformance(); +#endif // __cpp_lib_ranges + +template void check_default_operations() { + auto i = I{}; + { + auto& prefix = ++i; + EXPECT_EQ(std::addressof(i), std::addressof(prefix)); + } + { + auto& postfix = i++; + EXPECT_EQ(std::addressof(i), std::addressof(postfix)); + } + { + auto& deref = *i; + EXPECT_EQ(std::addressof(i), std::addressof(deref)); + } +} + +TEST(PrintIterator, DefaultOperators) { + check_default_operations(); + check_default_operations(); + check_default_operations(); + check_default_operations(); +#ifdef __cpp_char8_t + check_default_operations(); +#endif // __cpp_char8_t +} + +enum class decorator { off, on }; + +template +void check_assignment(fmt::print_iterator simple_writer, std::FILE* out); + +template <> +void check_assignment(fmt::print_iterator simple_writer, + std::FILE* out) { + EXPECT_WRITE(out, simple_writer = 0, "0"); + EXPECT_WRITE(out, simple_writer = 0.0, "0.0"); + EXPECT_WRITE(out, simple_writer = "hello there", "hello there"); + + auto v = std::vector>{{0, 1, 2}, {3, 4, 5}, {6, 7, 8, 9}}; + EXPECT_WRITE(out, simple_writer = v, "{{0, 1, 2}, {3, 4, 5}, {6, 7, 8, 9}}"); + + // GCC 4.8 silencing + (void)simple_writer, (void)out; +} + +template <> +void check_assignment(fmt::print_iterator decorated_writer, + std::FILE* out) { + auto d = std::deque{16, 8, 4, 2, 1}; + EXPECT_WRITE(out, std::copy(begin(d), end(d), decorated_writer), + "-16!-8!-4!-2!-1!"); + EXPECT_WRITE(out, decorated_writer = 0.0, "-0.0!"); + EXPECT_WRITE(out, decorated_writer = "Pusheen the Cat", "-Pusheen the Cat!"); + + // GCC 4.8 silencing + (void)decorated_writer, (void)out; +} + +TEST(PrintIterator, DefaultDestinationAssignment) { + check_assignment(fmt::print_iterator("{}"), stdout); + check_assignment(fmt::print_iterator("-{}!"), stdout); + + check_assignment(fmt::print_iterator(stderr, "{}"), stderr); + check_assignment(fmt::print_iterator(stderr, "-{}!"), stderr); +} +} // namespace