diff --git a/include/fmt/format.h b/include/fmt/format.h index 76ceee4ba3c9..fd1b7b1f2687 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3395,6 +3395,51 @@ inline typename buffer_context::type::iterator format_to( basic_format_args(as)); } +namespace internal { + +// Detect the iterator category of *any* given type in a SFINAE-friendly way. +// Unfortunately, older implementations of std::iterator_traits are not safe +// for use in a SFINAE-context. + +// the gist of C++17's void_t magic +template +struct void_ { typedef void type; }; + +template +struct it_category : std::false_type {}; + +template +struct it_category { typedef std::random_access_iterator_tag type; }; + +template +struct it_category::type> { + typedef typename T::iterator_category type; +}; + +// Detect if *any* given type models the OutputIterator concept +template +class is_output_iterator { + // Check for mutability because all iterator categories derived from + // std::input_iterator_tag *may* also meet the requirements of an + // OutputIterator, thereby falling into the category of 'mutable iterators' + // [iterator.requirements.general] clause 4. + // The compiler reveals this property only at the point of *actually + // dereferencing* the iterator! + template + static decltype(*(internal::declval())) test(std::input_iterator_tag); + template + static char& test(std::output_iterator_tag); + template + static const char& test(...); + + typedef decltype(test(typename it_category::type{})) type; + typedef typename std::remove_reference::type result; +public: + static const bool value = !std::is_const::value; +}; + +} // internal + template //using format_context_t = basic_format_context; struct format_context_t { typedef basic_format_context type; }; @@ -3407,8 +3452,9 @@ struct format_args_t { }; template -inline OutputIt vformat_to( - OutputIt out, const String &format_str, +inline typename std::enable_if::value, + OutputIt>::type + vformat_to(OutputIt out, const String &format_str, typename format_args_t::type args) { typedef output_range range; return vformat_to>(range(out), @@ -3427,7 +3473,9 @@ inline OutputIt vformat_to( \endrst */ template -inline FMT_ENABLE_IF_STRING(S, OutputIt) +inline typename std::enable_if< + internal::is_string::value && + internal::is_output_iterator::value, OutputIt>::type format_to(OutputIt out, const S &format_str, const Args &... args) { internal::check_format_string(format_str); typedef typename format_context_t::type context; @@ -3463,7 +3511,9 @@ inline format_arg_store< } template -inline format_to_n_result vformat_to_n( +inline typename std::enable_if< + internal::is_output_iterator::value, + format_to_n_result>::type vformat_to_n( OutputIt out, std::size_t n, basic_string_view format_str, typename format_to_n_args::type args) { typedef internal::truncating_iterator It; @@ -3479,7 +3529,10 @@ inline format_to_n_result vformat_to_n( \endrst */ template -inline FMT_ENABLE_IF_STRING(S, format_to_n_result) +inline typename std::enable_if< + internal::is_string::value && + internal::is_output_iterator::value, + format_to_n_result>::type format_to_n(OutputIt out, std::size_t n, const S &format_str, const Args &... args) { internal::check_format_string(format_str); diff --git a/test/format-test.cc b/test/format-test.cc index d6bbd19c273c..db8a01c3bbcf 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -197,6 +197,24 @@ TEST(IteratorTest, TruncatingBackInserter) { EXPECT_EQ(buffer, "42"); } +TEST(IteratorTest, IsOutputIterator) { + EXPECT_TRUE(fmt::internal::is_output_iterator::value); + EXPECT_FALSE(fmt::internal::is_output_iterator::value); + EXPECT_FALSE(fmt::internal::is_output_iterator::value); + EXPECT_TRUE(fmt::internal::is_output_iterator< + std::back_insert_iterator>::value); + EXPECT_TRUE(fmt::internal::is_output_iterator< + std::string::iterator>::value); + EXPECT_FALSE(fmt::internal::is_output_iterator< + std::string::const_iterator>::value); + EXPECT_FALSE(fmt::internal::is_output_iterator>::value); + EXPECT_TRUE(fmt::internal::is_output_iterator< + std::list::iterator>::value); + EXPECT_FALSE(fmt::internal::is_output_iterator< + std::list::const_iterator>::value); + EXPECT_FALSE(fmt::internal::is_output_iterator::value); +} + TEST(MemoryBufferTest, Ctor) { basic_memory_buffer buffer; EXPECT_EQ(static_cast(0), buffer.size());