// Formatting library for C++ - experimental range support // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. // // Copyright (c) 2018 - present, Remotion (Igor Schulz) // All Rights Reserved // {fmt} support for ranges, containers and types tuple interface. #ifndef FMT_RANGES_H_ #define FMT_RANGES_H_ #include #include #include #include "format.h" FMT_BEGIN_NAMESPACE namespace detail { template OutputIterator copy(const RangeT& range, OutputIterator out) { for (auto it = range.begin(), end = range.end(); it != end; ++it) *out++ = *it; return out; } template OutputIterator copy(const char* str, OutputIterator out) { while (*str) *out++ = *str++; return out; } template OutputIterator copy(char ch, OutputIterator out) { *out++ = ch; return out; } template OutputIterator copy(wchar_t ch, OutputIterator out) { *out++ = ch; return out; } // Returns true if T has a std::string-like interface, like std::string_view. template class is_std_string_like { template static auto check(U* p) -> decltype((void)p->find('a'), p->length(), (void)p->data(), int()); template static void check(...); public: static constexpr const bool value = is_string::value || std::is_convertible>::value || !std::is_void(nullptr))>::value; }; template struct is_std_string_like> : std::true_type {}; template class is_map { template static auto check(U*) -> typename U::mapped_type; template static void check(...); public: #ifdef FMT_FORMAT_MAP_AS_LIST static constexpr const bool value = false; #else static constexpr const bool value = !std::is_void(nullptr))>::value; #endif }; template class is_set { template static auto check(U*) -> typename U::key_type; template static void check(...); public: #ifdef FMT_FORMAT_SET_AS_LIST static constexpr const bool value = false; #else static constexpr const bool value = !std::is_void(nullptr))>::value && !is_map::value; #endif }; template struct conditional_helper {}; template struct is_range_ : std::false_type {}; #if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800 # define FMT_DECLTYPE_RETURN(val) \ ->decltype(val) { return val; } \ static_assert( \ true, "") // This makes it so that a semicolon is required after the // macro, which helps clang-format handle the formatting. // C array overload template auto range_begin(const T (&arr)[N]) -> const T* { return arr; } template auto range_end(const T (&arr)[N]) -> const T* { return arr + N; } template struct has_member_fn_begin_end_t : std::false_type {}; template struct has_member_fn_begin_end_t().begin()), decltype(std::declval().end())>> : std::true_type {}; // Member function overload template auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast(rng).begin()); template auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast(rng).end()); // ADL overload. Only participates in overload resolution if member functions // are not found. template auto range_begin(T&& rng) -> enable_if_t::value, decltype(begin(static_cast(rng)))> { return begin(static_cast(rng)); } template auto range_end(T&& rng) -> enable_if_t::value, decltype(end(static_cast(rng)))> { return end(static_cast(rng)); } template struct has_const_begin_end : std::false_type {}; template struct has_mutable_begin_end : std::false_type {}; template struct has_const_begin_end< T, void_t< decltype(detail::range_begin(std::declval&>())), decltype(detail::range_end(std::declval&>()))>> : std::true_type {}; template struct has_mutable_begin_end< T, void_t())), decltype(detail::range_end(std::declval())), enable_if_t::value>>> : std::true_type {}; template struct is_range_ : std::integral_constant::value || has_mutable_begin_end::value)> {}; # undef FMT_DECLTYPE_RETURN #endif // tuple_size and tuple_element check. template class is_tuple_like_ { template static auto check(U* p) -> decltype(std::tuple_size::value, int()); template static void check(...); public: static constexpr const bool value = !std::is_void(nullptr))>::value; }; // Check for integer_sequence #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900 template using integer_sequence = std::integer_sequence; template using index_sequence = std::index_sequence; template using make_index_sequence = std::make_index_sequence; #else template struct integer_sequence { using value_type = T; static FMT_CONSTEXPR size_t size() { return sizeof...(N); } }; template using index_sequence = integer_sequence; template struct make_integer_sequence : make_integer_sequence {}; template struct make_integer_sequence : integer_sequence {}; template using make_index_sequence = make_integer_sequence; #endif template using tuple_index_sequence = make_index_sequence::value>; template ::value> class is_tuple_formattable_ { public: static constexpr const bool value = false; }; template class is_tuple_formattable_ { template static std::true_type check2(index_sequence, integer_sequence); static std::false_type check2(...); template static decltype(check2( index_sequence{}, integer_sequence< bool, (is_formattable::type, C>::value)...>{})) check(index_sequence); public: static constexpr const bool value = decltype(check(tuple_index_sequence{}))::value; }; template void for_each(index_sequence, Tuple&& tup, F&& f) noexcept { using std::get; // using free function get(T) now. const int _[] = {0, ((void)f(get(tup)), 0)...}; (void)_; // blocks warnings } template FMT_CONSTEXPR make_index_sequence::value> get_indexes( T const&) { return {}; } template void for_each(Tuple&& tup, F&& f) { const auto indexes = get_indexes(tup); for_each(indexes, std::forward(tup), std::forward(f)); } #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 // Older MSVC doesn't get the reference type correctly for arrays. template struct range_reference_type_impl { using type = decltype(*detail::range_begin(std::declval())); }; template struct range_reference_type_impl { using type = T&; }; template using range_reference_type = typename range_reference_type_impl::type; #else template using range_reference_type = decltype(*detail::range_begin(std::declval())); #endif // We don't use the Range's value_type for anything, but we do need the Range's // reference type, with cv-ref stripped. template using uncvref_type = remove_cvref_t>; template using uncvref_first_type = remove_cvref_t< decltype(std::declval>().first)>; template using uncvref_second_type = remove_cvref_t< decltype(std::declval>().second)>; template OutputIt write_delimiter(OutputIt out) { *out++ = ','; *out++ = ' '; return out; } template auto write_range_entry(OutputIt out, basic_string_view str) -> OutputIt { return write_escaped_string(out, str); } template >::value)> inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt { auto sv = std_string_view(str); return write_range_entry(out, basic_string_view(sv)); } template ::value)> OutputIt write_range_entry(OutputIt out, const Arg v) { return write_escaped_char(out, v); } template < typename Char, typename OutputIt, typename Arg, FMT_ENABLE_IF(!is_std_string_like::type>::value && !std::is_same::value)> OutputIt write_range_entry(OutputIt out, const Arg& v) { return write(out, v); } } // namespace detail template struct is_tuple_like { static constexpr const bool value = detail::is_tuple_like_::value && !detail::is_range_::value; }; template struct is_tuple_formattable { static constexpr const bool value = detail::is_tuple_formattable_::value; }; template struct formatter::value && fmt::is_tuple_formattable::value>> { private: // C++11 generic lambda for format(). template struct format_each { template void operator()(const T& v) { if (i > 0) out = detail::write_delimiter(out); out = detail::write_range_entry(out, v); ++i; } int i; typename FormatContext::iterator& out; }; public: template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } template auto format(const TupleT& values, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); *out++ = '('; detail::for_each(values, format_each{0, out}); *out++ = ')'; return out; } }; template struct is_range { static constexpr const bool value = detail::is_range_::value && !detail::is_std_string_like::value && !detail::is_map::value && !std::is_convertible>::value && !std::is_constructible, T>::value; }; namespace detail { template struct range_mapper { using mapper = arg_mapper; template , Context>::value)> static auto map(T&& value) -> T&& { return static_cast(value); } template , Context>::value)> static auto map(T&& value) -> decltype(mapper().map(static_cast(value))) { return mapper().map(static_cast(value)); } }; template using range_formatter_type = conditional_t< is_formattable::value, formatter>{}.map( std::declval()))>, Char>, fallback_formatter>; template using maybe_const_range = conditional_t::value, const R, R>; } // namespace detail template struct formatter< R, Char, enable_if_t< conjunction // Workaround a bug in MSVC 2017 and earlier. #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 , disjunction< is_formattable>, Char>, detail::has_fallback_formatter< detail::uncvref_type>, Char> > #endif >::value >> { using range_type = detail::maybe_const_range; using formatter_type = detail::range_formatter_type>; formatter_type underlying_; bool custom_specs_ = false; template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { auto it = ctx.begin(); auto end = ctx.end(); if (it == end || *it == '}') return it; if (*it != ':') FMT_THROW(format_error("no top-level range formatters supported")); custom_specs_ = true; ++it; ctx.advance_to(it); return underlying_.parse(ctx); } template auto format(range_type& range, FormatContext& ctx) const -> decltype(ctx.out()) { Char prefix = detail::is_set::value ? '{' : '['; Char postfix = detail::is_set::value ? '}' : ']'; detail::range_mapper> mapper; auto out = ctx.out(); *out++ = prefix; int i = 0; auto it = detail::range_begin(range); auto end = detail::range_end(range); for (; it != end; ++it) { if (i > 0) out = detail::write_delimiter(out); if (custom_specs_) { ctx.advance_to(out); out = underlying_.format(mapper.map(*it), ctx); } else { out = detail::write_range_entry(out, *it); } ++i; } *out++ = postfix; return out; } }; template struct formatter< T, Char, enable_if_t // Workaround a bug in MSVC 2017 and earlier. #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 , disjunction< is_formattable, Char>, detail::has_fallback_formatter, Char> >, disjunction< is_formattable, Char>, detail::has_fallback_formatter, Char> > #endif >::value >> { template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } template < typename FormatContext, typename U, FMT_ENABLE_IF( std::is_same::value, const T, T>>::value)> auto format(U& map, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); *out++ = '{'; int i = 0; for (const auto& item : map) { if (i > 0) out = detail::write_delimiter(out); out = detail::write_range_entry(out, item.first); *out++ = ':'; *out++ = ' '; out = detail::write_range_entry(out, item.second); ++i; } *out++ = '}'; return out; } }; template struct tuple_join_view : detail::view { const std::tuple& tuple; basic_string_view sep; tuple_join_view(const std::tuple& t, basic_string_view s) : tuple(t), sep{s} {} }; template using tuple_arg_join = tuple_join_view; // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers // support in tuple_join. It is disabled by default because of issues with // the dynamic width and precision. #ifndef FMT_TUPLE_JOIN_SPECIFIERS # define FMT_TUPLE_JOIN_SPECIFIERS 0 #endif template struct formatter, Char> { template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { return do_parse(ctx, std::integral_constant()); } template auto format(const tuple_join_view& value, FormatContext& ctx) const -> typename FormatContext::iterator { return do_format(value, ctx, std::integral_constant()); } private: std::tuple::type, Char>...> formatters_; template FMT_CONSTEXPR auto do_parse(ParseContext& ctx, std::integral_constant) -> decltype(ctx.begin()) { return ctx.begin(); } template FMT_CONSTEXPR auto do_parse(ParseContext& ctx, std::integral_constant) -> decltype(ctx.begin()) { auto end = ctx.begin(); #if FMT_TUPLE_JOIN_SPECIFIERS end = std::get(formatters_).parse(ctx); if (N > 1) { auto end1 = do_parse(ctx, std::integral_constant()); if (end != end1) FMT_THROW(format_error("incompatible format specs for tuple elements")); } #endif return end; } template auto do_format(const tuple_join_view&, FormatContext& ctx, std::integral_constant) const -> typename FormatContext::iterator { return ctx.out(); } template auto do_format(const tuple_join_view& value, FormatContext& ctx, std::integral_constant) const -> typename FormatContext::iterator { auto out = std::get(formatters_) .format(std::get(value.tuple), ctx); if (N > 1) { out = std::copy(value.sep.begin(), value.sep.end(), out); ctx.advance_to(out); return do_format(value, ctx, std::integral_constant()); } return out; } }; FMT_MODULE_EXPORT_BEGIN /** \rst Returns an object that formats `tuple` with elements separated by `sep`. **Example**:: std::tuple t = {1, 'a'}; fmt::print("{}", fmt::join(t, ", ")); // Output: "1, a" \endrst */ template FMT_CONSTEXPR auto join(const std::tuple& tuple, string_view sep) -> tuple_join_view { return {tuple, sep}; } template FMT_CONSTEXPR auto join(const std::tuple& tuple, basic_string_view sep) -> tuple_join_view { return {tuple, sep}; } /** \rst Returns an object that formats `initializer_list` with elements separated by `sep`. **Example**:: fmt::print("{}", fmt::join({1, 2, 3}, ", ")); // Output: "1, 2, 3" \endrst */ template auto join(std::initializer_list list, string_view sep) -> join_view { return join(std::begin(list), std::end(list), sep); } FMT_MODULE_EXPORT_END FMT_END_NAMESPACE #endif // FMT_RANGES_H_