diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -129,6 +129,7 @@ __debug __errc __format/buffer.h + __format/enable_insertable.h __format/format_arg.h __format/format_args.h __format/format_context.h diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h --- a/libcxx/include/__format/buffer.h +++ b/libcxx/include/__format/buffer.h @@ -11,9 +11,12 @@ #define _LIBCPP___FORMAT_BUFFER_H #include <__config> +#include <__format/enable_insertable.h> #include <__format/formatter.h> // for __char_type TODO FMT Move the concept? +#include <__iterator/back_insert_iterator.h> #include <__iterator/concepts.h> #include <__utility/move.h> +#include #include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -95,6 +98,98 @@ _OutIt __it_; }; +/// Concept to see whether a \a _Container is insertable. +/// +/// The concept is used to validate whether multiple calls to a +/// \ref back_insert_iterator can be replace by a call to \c _Container::insert. +/// +/// \note a \a _Container needs to opt-in to the concept by specializing +/// \ref __enable_insertable. +template +concept __insertable = __enable_insertable<_Container> && + __formatter::__char_type && + requires(_Container& __t, + add_pointer_t __first, + add_pointer_t __last) { + __t.insert(__t.end(), __first, __last); +}; + +/// Extract the container type of a \ref back_insert_iterator. +template +struct _LIBCPP_TEMPLATE_VIS __back_insert_iterator_container { + using type = void; +}; + +template <__insertable _Container> +struct _LIBCPP_TEMPLATE_VIS + __back_insert_iterator_container> { + using type = _Container; +}; + +/// Helper to get the container of a \ref back_insert_iterator. +template +class _LIBCPP_TEMPLATE_VIS __container_extractor + : private back_insert_iterator<_Container> { +public: + _LIBCPP_HIDE_FROM_ABI explicit __container_extractor( + back_insert_iterator<_Container> __it) + : back_insert_iterator<_Container>(__it) {} + + _LIBCPP_HIDE_FROM_ABI _Container* get() { return this->container; } +}; + +/// A buffer that wraps a \ref back_insert_iterator for a \a _Container. +template +class _LIBCPP_TEMPLATE_VIS __container_buffer { +public: + using _CharT = typename _Container::value_type; + + _LIBCPP_HIDE_FROM_ABI explicit __container_buffer( + back_insert_iterator<_Container> __it) + : __container_(__container_extractor<_Container>{__it}.get()) {} + + _LIBCPP_HIDE_FROM_ABI void put(_CharT __c) { + *__buffer_it_++ = __c; + // Profiling showed flushing after adding is more efficient than flushing + // when entering the function. + if (__buffer_it_ == __buffer_.end()) + __flush(); + } + + _LIBCPP_HIDE_FROM_ABI auto out() { + __flush(); + return back_inserter(*__container_); + } + +private: + /// The size of the internal buffer. + /// + /// Profiling shows a 256 byte buffer seems optimal. + /// (256 elements for a char buffer and 64 elements for a 4-byte wchar_t) + static constexpr size_t __buffer_size = 256 / sizeof(_CharT); + + array<_CharT, __buffer_size> __buffer_{}; + _CharT* __buffer_it_{__buffer_.begin()}; + _Container* __container_; + + void __flush() { + // Use __buffer_it_ instead of end() since the buffer might be partly filled. + __container_->insert(__container_->end(), __buffer_.begin(), __buffer_it_); + __buffer_it_ = __buffer_.begin(); + } +}; + +/// Selects the type of the buffer for the output iterator. +template +class _LIBCPP_TEMPLATE_VIS __buffer_selector { + using _Iterator = typename __back_insert_iterator_container<_OutIt>::type; + +public: + using type = conditional_t, + __iterator_wrapper_buffer<_OutIt, _CharT>, + __container_buffer<_Iterator>>; +}; + } // namespace __format #endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) diff --git a/libcxx/include/__format/enable_insertable.h b/libcxx/include/__format/enable_insertable.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/enable_insertable.h @@ -0,0 +1,35 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FORMAT_ENABLE_INSERTABLE_H +#define _LIBCPP___FORMAT_ENABLE_INSERTABLE_H + +#include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +namespace __format { + +/// Opt-in to enable \ref __insertable for a \a _Container. +template +inline constexpr bool __enable_insertable = false; + +} // namespace __format + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FORMAT_ENABLE_INSERTABLE_H diff --git a/libcxx/include/deque b/libcxx/include/deque --- a/libcxx/include/deque +++ b/libcxx/include/deque @@ -162,6 +162,7 @@ #include <__config> #include <__debug> +#include <__format/enable_insertable.h> #include <__split_buffer> #include <__utility/forward.h> #include @@ -3018,6 +3019,20 @@ __c.erase(_VSTD::remove_if(__c.begin(), __c.end(), __pred), __c.end()); return __old_size - __c.size(); } + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +# if !defined(_LIBCPP_HAS_NO_CONCEPTS) + +template <> +inline constexpr bool __format::__enable_insertable> = true; +template <> +inline constexpr bool __format::__enable_insertable> = true; + +# endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + #endif diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -267,6 +267,7 @@ #include <__config> #include <__debug> #include <__format/buffer.h> +#include <__format/enable_insertable.h> #include <__format/format_arg.h> #include <__format/format_args.h> #include <__format/format_context.h> @@ -496,7 +497,7 @@ _OutIt __out_it, basic_string_view<_CharT> __fmt, basic_format_args> __args) { - using _Buffer = __format::__iterator_wrapper_buffer<_OutIt, _CharT>; + using _Buffer = typename __format::__buffer_selector<_OutIt, _CharT>::type; _Buffer __buffer{_VSTD::move(__out_it)}; _VSTD::__format::__vformat_to( basic_format_parse_context{__fmt, __args.__size()}, @@ -626,7 +627,7 @@ _OutIt __out_it, locale __loc, basic_string_view<_CharT> __fmt, basic_format_args> __args) { - using _Buffer = __format::__iterator_wrapper_buffer<_OutIt, _CharT>; + using _Buffer = typename __format::__buffer_selector<_OutIt, _CharT>::type; _Buffer __buffer{_VSTD::move(__out_it)}; _VSTD::__format::__vformat_to( basic_format_parse_context{__fmt, __args.__size()}, diff --git a/libcxx/include/list b/libcxx/include/list --- a/libcxx/include/list +++ b/libcxx/include/list @@ -182,6 +182,7 @@ #include <__config> #include <__debug> +#include <__format/enable_insertable.h> #include <__utility/forward.h> #include #include @@ -2485,6 +2486,20 @@ erase(list<_Tp, _Allocator>& __c, const _Up& __v) { return _VSTD::erase_if(__c, [&](auto& __elem) { return __elem == __v; }); } + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +# if !defined(_LIBCPP_HAS_NO_CONCEPTS) + +template <> +inline constexpr bool __format::__enable_insertable> = true; +template <> +inline constexpr bool __format::__enable_insertable> = true; + +# endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + #endif _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -439,6 +439,7 @@ export locale } module buffer { private header "__format/buffer.h" } + module enable_intertable { private header "__format/enable_insertable.h" } module format_error { private header "__format/format_error.h" } module format_fwd { private header "__format/format_fwd.h" } module format_parse_context { private header "__format/format_parse_context.h" } diff --git a/libcxx/include/string b/libcxx/include/string --- a/libcxx/include/string +++ b/libcxx/include/string @@ -518,6 +518,7 @@ #include <__config> #include <__debug> #include <__functional_base> +#include <__format/enable_insertable.h> #include <__iterator/wrap_iter.h> #include #include @@ -4578,6 +4579,23 @@ } #endif +#if _LIBCPP_STD_VER > 17 + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +# if !defined(_LIBCPP_HAS_NO_CONCEPTS) + +template <> +inline constexpr bool __format::__enable_insertable> = true; +template <> +inline constexpr bool __format::__enable_insertable> = true; + +# endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + +#endif //_LIBCPP_STD_VER > 17 + _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS diff --git a/libcxx/include/vector b/libcxx/include/vector --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -274,6 +274,7 @@ #include <__config> #include <__bit_reference> #include <__debug> +#include <__format/enable_insertable.h> #include <__functional_base> #include <__iterator/wrap_iter.h> #include <__split_buffer> @@ -3429,6 +3430,23 @@ } #endif +#if _LIBCPP_STD_VER > 17 + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +# if !defined(_LIBCPP_HAS_NO_CONCEPTS) + +template <> +inline constexpr bool __format::__enable_insertable> = true; +template <> +inline constexpr bool __format::__enable_insertable> = true; + +# endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + +#endif //_LIBCPP_STD_VER > 17 + _LIBCPP_END_NAMESPACE_STD _LIBCPP_POP_MACROS diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/enable_insertable.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/enable_insertable.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/enable_insertable.module.verify.cpp @@ -0,0 +1,16 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__format/enable_insertable.h'}} +#include <__format/enable_insertable.h> diff --git a/libcxx/test/libcxx/utilities/format/enable_insertable.compile.pass.cpp b/libcxx/test/libcxx/utilities/format/enable_insertable.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/enable_insertable.compile.pass.cpp @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +template +struct no_value_type { + using iterator = CharT*; + iterator end(); + void insert(iterator, CharT*, CharT*); +}; + +template +struct no_end { + using value_type = CharT; + using iterator = CharT*; + void insert(iterator, CharT*, CharT*); +}; + +template +struct no_insert { + using value_type = CharT; + using iterator = CharT*; + iterator end(); +}; + +template +struct no_specialization { + using value_type = CharT; + using iterator = CharT*; + iterator end(); + void insert(iterator, CharT*, CharT*); +}; + +template +struct valid { + using value_type = CharT; + using iterator = CharT*; + iterator end(); + void insert(iterator, CharT*, CharT*); +}; + +namespace std::__format { +template <> +inline constexpr bool __enable_insertable> = true; +template <> +inline constexpr bool __enable_insertable> = true; +template <> +inline constexpr bool __enable_insertable> = true; +template <> +inline constexpr bool __enable_insertable> = true; +template <> +inline constexpr bool __enable_insertable> = true; +template <> +inline constexpr bool __enable_insertable> = true; +template <> +inline constexpr bool __enable_insertable> = true; +template <> +inline constexpr bool __enable_insertable> = true; +} // namespace std::__format + +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(std::__format::__insertable>); +static_assert(std::__format::__insertable>); + +namespace std::__format { +template <> +inline constexpr bool __enable_insertable> = true; +template <> +inline constexpr bool __enable_insertable> = true; +} // namespace std::__format + +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); + +static_assert(std::__format::__insertable); +static_assert(std::__format::__insertable); +static_assert(!std::__format::__insertable); +static_assert(!std::__format::__insertable); + +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); + +static_assert(std::__format::__insertable>); +static_assert(std::__format::__insertable>); +static_assert(std::__format::__insertable>); +static_assert(std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(std::__format::__insertable>); +static_assert(std::__format::__insertable>); + +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); + +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>); + +static_assert(!std::__format::__insertable>); +static_assert(!std::__format::__insertable>);