diff --git a/doc/qbk/0.main.qbk b/doc/qbk/0.main.qbk
index 5426029a6..28db40901 100644
--- a/doc/qbk/0.main.qbk
+++ b/doc/qbk/0.main.qbk
@@ -50,9 +50,12 @@
[def __MoveConstructible__ [@https://en.cppreference.com/w/cpp/named_req/MoveConstructible ['MoveConstructible]]]
[def __SemiRegular__ [@https://en.cppreference.com/w/cpp/concepts/semiregular ['SemiRegular]]]
[def __Swappable__ [@https://en.cppreference.com/w/cpp/named_req/Swappable ['Swappable]]]
-[def __CharSet__ [link url.grammar_rules.charset ['CharSet]]]
+[def __CharSet__ [link url.concepts.charset ['CharSet]]]
+[def __MutableString__ [link url.concepts.mutablestring ['MutableString]]]
[def __std_swap__ [@https://en.cppreference.com/w/cpp/algorithm/swap `std::swap`]]
+[def __std_string__ [@https://en.cppreference.com/w/cpp/string/basic_string `std::string`]]
+
[def __authority_view__ [link url.ref.boost__urls__authority_view `authority_view`]]
[def __segments_view__ [link url.ref.boost__urls__segments_view `segments_view`]]
[def __segments_encoded_view__ [link url.ref.boost__urls__segments_encoded_view `segments_encoded_view`]]
@@ -63,6 +66,7 @@
[def __static_pool__ [link url.ref.boost__urls__static_pool `static_pool`]]
[def __static_url__ [link url.ref.boost__urls__static_url `static_url`]]
[def __const_string__ [link url.ref.boost__urls__const_string `const_string`]]
+[def __pct_encoded_view__ [link url.ref.boost__urls__pct_encoded_view `pct_encoded_view`]]
[def __string_view__ [link url.ref.boost__urls__string_view `string_view`]]
[def __url__ [link url.ref.boost__urls__url `url`]]
[def __url_view__ [link url.ref.boost__urls__url_view `url_view`]]
@@ -97,7 +101,6 @@
[section Grammar Rules]
[include 5.0.grammars.qbk]
[include 5.1.customization.qbk]
-[include 5.2.CharSet.qbk]
[endsect]
@@ -108,4 +111,11 @@
[block'''''']
[endsect]
+[section:concepts Concepts]
+[include 6.1.CharSet.qbk]
+[include 6.2.MutableString.qbk]
+[endsect]
+
+
+
[xinclude index.xml]
diff --git a/doc/qbk/5.2.CharSet.qbk b/doc/qbk/6.1.CharSet.qbk
similarity index 100%
rename from doc/qbk/5.2.CharSet.qbk
rename to doc/qbk/6.1.CharSet.qbk
diff --git a/doc/qbk/6.2.MutableString.qbk b/doc/qbk/6.2.MutableString.qbk
new file mode 100644
index 000000000..9c586a6e0
--- /dev/null
+++ b/doc/qbk/6.2.MutableString.qbk
@@ -0,0 +1,86 @@
+[/
+ Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
+
+ Distributed under the Boost Software License, Version 1.0. (See accompanying
+ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+ Official repository: https://github.com/CPPAlliance/url
+]
+
+[section MutableString]
+
+A ['MutableString] is a container that has the `assign` and
+`append` member functions that accept iterators to `char`.
+
+The class __pct_encoded_view__ provides a operations
+to assign and append percent-decoded values to a
+['MutableString].
+
+When storing decoded values in a container, these functions
+allow the caller to recycle the input string without
+reallocating memory.
+
+[heading Related Identifiers]
+
+* __pct_encoded_view__
+* [link url.ref.boost__urls__pct_encoded_rule `pct_encoded_rule`]
+
+[heading Requirements]
+
+In this table:
+
+* `T` is a type meeting the requirements of ['MutableString]
+* `s` is a mutable container of type `T`
+* `first`, `last` are __InputIterator__s whose `value_type` is `char`
+ and which refer to the valid character sequence `[ first, last )`
+
+[table Valid expressions
+[[Expression] [Type] [Semantics, Pre/Post-conditions]]
+[
+ [
+ ```
+ T::value_type
+ ```
+ ]
+ [`char`]
+ [
+ Requires: `std::is_same_v< T::value_type, char >`
+ ]
+][
+ [
+ ```
+ s.assign(first,last)
+ ```
+ ]
+ []
+ [
+ This member function assigns the characters from the
+ range in `[first, last)` to `s`.
+ ]
+][
+ [
+ ```
+ s.append(first,last)
+ ```
+ ]
+ []
+ [
+ This member function appends the characters from the
+ range in `[first, last)` to `s`.
+ ]
+]]
+
+[heading Exemplar]
+
+[snippet_mutable_string_exemplar]
+
+[heading Models]
+
+* __std_string__
+
+[heading Models]
+
+* __pct_encoded_view__
+* [def __pct_encoded_view__ [link url.ref.boost__urls__pct_encoded_view `pct_encoded_view`]]
+
+[endsect]
diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml
index bc9f4da74..ac0d3f75e 100644
--- a/doc/qbk/quickref.xml
+++ b/doc/qbk/quickref.xml
@@ -28,6 +28,7 @@
authority_view
const_string
const_string::factory
+ pct_encoded_view
ipv4_address
ipv6_address
params
@@ -106,7 +107,8 @@
Concepts
- CharSet
+ CharSet
+ MutableString
@@ -224,6 +226,7 @@
Type Traits
+ is_mutable_string
is_charset
is_range
diff --git a/doc/xsl/custom-overrides.xsl b/doc/xsl/custom-overrides.xsl
index e0bb488e1..878f978c4 100644
--- a/doc/xsl/custom-overrides.xsl
+++ b/doc/xsl/custom-overrides.xsl
@@ -25,7 +25,8 @@
diff --git a/include/boost/url/grammar/is_mutable_string.hpp b/include/boost/url/grammar/is_mutable_string.hpp
new file mode 100644
index 000000000..814cd0a94
--- /dev/null
+++ b/include/boost/url/grammar/is_mutable_string.hpp
@@ -0,0 +1,78 @@
+//
+// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// Official repository: https://github.com/CPPAlliance/url
+//
+
+#ifndef BOOST_URL_IS_MUTABLE_STRING_HPP
+#define BOOST_URL_IS_MUTABLE_STRING_HPP
+
+#include
+
+namespace boost {
+namespace urls {
+namespace grammar {
+
+/** Alias for `std::true_type` if `T` satisfies __MutableString__
+ for the iterator type `I`
+*/
+#ifdef BOOST_URL_DOCS
+template
+using is_mutable_string = __see_below__;
+#else
+namespace detail
+{
+template
+struct has_value_type : std::false_type {};
+
+template
+struct has_value_type>
+ : std::true_type
+{};
+
+template
+struct has_assign_and_append : std::false_type {};
+
+template
+struct has_assign_and_append()
+ .append(
+ std::declval(),
+ std::declval())
+ ),
+ // T::assign(I, I)
+ decltype(
+ std::declval()
+ .assign(
+ std::declval(),
+ std::declval())
+ )>>
+ : std::true_type
+{};
+}
+
+template
+struct is_mutable_string : std::false_type {};
+
+template
+struct is_mutable_string<
+ T, I,
+ typename std::enable_if<
+ detail::has_value_type::value &&
+ detail::has_assign_and_append::value
+ >::type>
+ : std::is_same
+{};
+#endif
+
+} // grammar
+} // urls
+} // boost
+
+#endif
diff --git a/include/boost/url/impl/pct_encoded_view.hpp b/include/boost/url/impl/pct_encoded_view.hpp
new file mode 100644
index 000000000..9afa264bc
--- /dev/null
+++ b/include/boost/url/impl/pct_encoded_view.hpp
@@ -0,0 +1,202 @@
+//
+// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// Official repository: https://github.com/CPPAlliance/url
+//
+
+#ifndef BOOST_URL_IMPL_PCT_ENCODED_VIEW_HPP
+#define BOOST_URL_IMPL_PCT_ENCODED_VIEW_HPP
+
+#include
+
+namespace boost {
+namespace urls {
+
+class pct_encoded_view::iterator
+{
+ char const* begin_{nullptr};
+ char const* pos_{nullptr};
+ bool plus_to_space_{true};
+
+ friend pct_encoded_view;
+
+ iterator(
+ char const* str,
+ bool plus_to_space) noexcept
+ : begin_(str)
+ , pos_(str)
+ , plus_to_space_(plus_to_space)
+ {}
+
+ // end ctor
+ iterator(
+ char const* str,
+ size_type n,
+ bool plus_to_space) noexcept
+ : begin_(str)
+ , pos_(str + n)
+ , plus_to_space_(plus_to_space)
+ {}
+
+public:
+ using value_type = char;
+ using reference = char;
+ using pointer = void const*;
+ using const_reference = char;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using iterator_category =
+ std::bidirectional_iterator_tag;
+
+ iterator() = default;
+
+ iterator(iterator const&) = default;
+
+ iterator&
+ operator=(iterator const&) = default;
+
+ BOOST_URL_DECL
+ reference
+ operator*() const noexcept;
+
+ iterator&
+ operator++() noexcept
+ {
+ BOOST_ASSERT(pos_ != nullptr);
+ if (*pos_ != '%')
+ ++pos_;
+ else
+ pos_ += 3;
+ return *this;
+ }
+
+ iterator&
+ operator--() noexcept
+ {
+ BOOST_ASSERT(pos_ != begin_);
+ if (pos_ - begin_ < 3 ||
+ pos_[-3] != '%')
+ --pos_;
+ else
+ pos_ -= 3;
+ return *this;
+ }
+
+ iterator
+ operator++(int) noexcept
+ {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ iterator
+ operator--(int) noexcept
+ {
+ auto tmp = *this;
+ --*this;
+ return tmp;
+ }
+
+ bool
+ operator==(
+ iterator const& other) const noexcept
+ {
+ return pos_ == other.pos_;
+ }
+
+ bool
+ operator!=(
+ iterator const& other) const noexcept
+ {
+ return !(*this == other);
+ }
+};
+
+inline
+auto
+pct_encoded_view::
+begin() const noexcept ->
+ const_iterator
+{
+ return {p_, plus_to_space_};
+}
+
+inline
+auto
+pct_encoded_view::
+end() const noexcept ->
+ const_iterator
+{
+ return {p_, n_, plus_to_space_};
+}
+
+inline
+auto
+pct_encoded_view::
+front() const noexcept ->
+ const_reference
+{
+ BOOST_ASSERT( !empty() );
+ return *begin();
+}
+
+inline
+auto
+pct_encoded_view::
+back() const noexcept ->
+ const_reference
+{
+ BOOST_ASSERT( !empty() );
+ return *--end();
+}
+
+template
+MutableString&
+pct_encoded_view::
+assign_to(
+ MutableString& s) const
+{
+ // If you get a compiler error here, it means that
+ // your String type is missing the necessary append
+ // or assign member functions, or that your
+ // value_type is not char!
+ //
+ // This function should only be used
+ // if the container `MutableString` has the
+ // member function `assign(iterator, iterator)`.
+ BOOST_STATIC_ASSERT(
+ grammar::is_mutable_string<
+ MutableString, iterator>::value);
+ s.assign(begin(), end());
+ return s;
+}
+
+template
+MutableString&
+pct_encoded_view::
+append_to(
+ MutableString& s) const
+{
+ // If you get a compiler error here, it means that
+ // your String type is missing the necessary append
+ // or assign member functions, or that your
+ // value_type is not char!
+ //
+ // This function should only be used
+ // if the container `MutableString` has the
+ // member function `append(iterator, iterator)`.
+ BOOST_STATIC_ASSERT(
+ grammar::is_mutable_string<
+ MutableString, iterator>::value);
+ s.append(begin(), end());
+ return s;
+}
+
+} // urls
+} // boost
+
+#endif
diff --git a/include/boost/url/impl/pct_encoded_view.ipp b/include/boost/url/impl/pct_encoded_view.ipp
new file mode 100644
index 000000000..71c6558b5
--- /dev/null
+++ b/include/boost/url/impl/pct_encoded_view.ipp
@@ -0,0 +1,136 @@
+//
+// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// Official repository: https://github.com/CPPAlliance/url
+//
+
+#ifndef BOOST_URL_IMPL_PCT_ENCODED_VIEW_IPP
+#define BOOST_URL_IMPL_PCT_ENCODED_VIEW_IPP
+
+#include
+#include
+
+namespace boost {
+namespace urls {
+
+auto
+pct_encoded_view::iterator::
+operator*() const noexcept ->
+ reference
+{
+ if (plus_to_space_ &&
+ *pos_ == '+')
+ return ' ';
+ if (*pos_ != '%')
+ return *pos_;
+ char d0;
+ grammar::hexdig_value(pos_[1], d0);
+ char d1;
+ grammar::hexdig_value(pos_[2], d1);
+ return static_cast(
+ ((static_cast<
+ unsigned char>(d0) << 4) +
+ (static_cast<
+ unsigned char>(d1))));
+}
+
+pct_encoded_view::
+pct_encoded_view(
+ string_view str,
+ pct_decode_opts opt) noexcept
+ : p_(str.data())
+ , n_(str.size())
+ , plus_to_space_(opt.plus_to_space)
+{
+ error_code ec;
+ opt.non_normal_is_error = false;
+ dn_ = validate_pct_encoding(
+ str, ec, opt,
+ [](unsigned char c) { return c != '%'; });
+ if (ec.failed())
+ detail::throw_invalid_argument(
+ BOOST_CURRENT_LOCATION);
+}
+
+auto
+pct_encoded_view::
+copy(
+ char* dest,
+ size_type count,
+ size_type pos) const ->
+ size_type
+{
+ if( pos > size() )
+ detail::throw_invalid_argument(
+ BOOST_CURRENT_LOCATION);
+ std::size_t rlen = (std::min)(count, size() - pos);
+ auto first = std::next(begin(), pos);
+ auto last = std::next(first, rlen);
+ while (first != last)
+ *dest++ = *first++;
+ return rlen;
+}
+
+namespace detail
+{
+template
+int
+decoded_strcmp(pct_encoded_view s0, T s1)
+{
+ std::size_t rlen =
+ (std::min)(s0.size(), s1.size());
+ auto it0 = s0.begin();
+ auto it1 = s1.begin();
+ char c0;
+ char c1;
+ std::size_t i = 0;
+ for (; i < rlen; ++i)
+ {
+ c0 = *it0;
+ c1 = *it1;
+ if (c0 != c1)
+ break;
+ ++it0;
+ ++it1;
+ }
+ if (i != rlen)
+ return 1 - (c0 == c1) -
+ 2 * (static_cast(c0)
+ < static_cast(c1));
+ return 1 - (s0.size() == s1.size()) -
+ 2 * (s0.size() < s1.size());
+}
+}
+
+int
+pct_encoded_view::
+compare(string_view other) const noexcept
+{
+ return detail::decoded_strcmp(*this, other);
+}
+
+int
+pct_encoded_view::
+compare(pct_encoded_view other) const noexcept
+{
+ return detail::decoded_strcmp(*this, other);
+}
+
+std::ostream&
+operator<<(
+ std::ostream& os,
+ pct_encoded_view const& str) {
+ auto it = str.begin();
+ auto end = str.end();
+ for (; it != end; ++it)
+ os.put(*it);
+ return os;
+}
+
+} // urls
+} // boost
+
+#endif
diff --git a/include/boost/url/pct_encoded_view.hpp b/include/boost/url/pct_encoded_view.hpp
new file mode 100644
index 000000000..f6a83bd45
--- /dev/null
+++ b/include/boost/url/pct_encoded_view.hpp
@@ -0,0 +1,715 @@
+//
+// Copyright (c) 2022 Alan Freitas (alandefreitas@gmail.com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// Official repository: https://github.com/CPPAlliance/url
+//
+
+#ifndef BOOST_URL_PCT_ENCODED_VIEW_HPP
+#define BOOST_URL_PCT_ENCODED_VIEW_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace boost {
+namespace urls {
+
+/** A view of the percent decoded characters in a string
+
+ Objects of type pct_encoded_view reference URL
+ or other component strings that are
+ percent-encoded. That is, the characters
+ which are special (not in the allowed
+ character set) are represented with escapes
+ that start with a percent followed by a
+ two-digit hexadecimal number of the
+ corresponding character code
+ (which may be part of a UTF-8 code
+ point depending on the context).
+
+ The primary operations are:
+
+ @li Comparison to other percent-encoded or plain strings
+ @li Appending the decoded characters to an existing container
+ @li Assigning the decoded characters to an existing container
+ @li Iterating over the represented decoded character range
+ @li Accessing the underlying percent-encoded character buffer
+
+ These objects can only be constructed from
+ strings that have a valid percent-encoding,
+ otherwise construction fails.
+
+ The pct_encoded_view iterators operate lazily on
+ underlying sequence transforming groups
+ of percent-encoded characters into single
+ decoded elements.
+
+ The caller must opt-in to perform expensive,
+ possibly-allocating decoding operations which
+ produce a contiguous character buffer by
+ calling @ref append_to, @ref assign_to, or
+ @ref to_string.
+
+ Any operation on the underlying percent-encoded
+ strings invalidates the view iterators. The view
+ should be recreated after any operation that
+ may mutate the underlying range.
+
+*/
+class pct_encoded_view
+{
+ char const* p_ = nullptr;
+ std::size_t n_ = 0;
+ std::size_t dn_ = 0;
+ bool plus_to_space_ = true;
+
+ using traits_type = std::char_traits;
+
+public:
+ /** Type of a decoded character
+ */
+ using value_type = char;
+
+ /// @copydoc value_type
+ using reference = char;
+
+ /// @copydoc value_type
+ using const_reference = char;
+
+ /** The unsigned integer type used to represent size.
+ */
+ using size_type = std::size_t;
+
+ /** The signed integer type used to represent differences.
+ */
+ using difference_type = std::ptrdiff_t;
+
+ /** A read-only bidirectional iterator to the decoded range.
+
+ This is a read-only bidirectional iterator to
+ the decoded characters.
+
+ Any operation on the underlying percent-encoded
+ strings invalidates the view iterators. The view
+ should be recreated after any operation that
+ may mutate the underlying range.
+
+ */
+#ifdef BOOST_URL_DOCS
+ using iterator = __see_below__;
+#else
+ class iterator;
+#endif
+
+ /// @copydoc iterator
+ using const_iterator = iterator;
+
+ /** Constructor
+
+ Default-constructed objects represent
+ the empty string.
+
+ */
+ explicit
+ pct_encoded_view(
+ pct_decode_opts const& opt = {}) noexcept
+ : plus_to_space_(opt.plus_to_space)
+ {}
+
+ /** Constructor
+ */
+ BOOST_URL_DECL
+ explicit
+ pct_encoded_view(
+ string_view str,
+ pct_decode_opts opt = {}) noexcept;
+
+ /** Returns an iterator to the beginning
+ */
+ const_iterator
+ begin() const noexcept;
+
+ /** Returns an iterator to the end
+ */
+ const_iterator
+ end() const noexcept;
+
+ /** Return the first decoded character
+
+ @par Preconditions
+
+ `empty() != true`.
+
+ @return the first character
+ */
+ const_reference
+ front() const noexcept;
+
+ /** Return the last decoded character
+
+ @par Preconditions
+
+ `empty() != true`.
+
+ @return the last character
+ */
+ const_reference
+ back() const noexcept;
+
+ /** Returns the encoded string view
+
+ This function returns a string_view of
+ the underlying encoded string.
+
+ @return The encoded string view.
+ */
+ string_view
+ encoded() const noexcept
+ {
+ return {p_, n_};
+ }
+
+ /** Returns the number of char elements in the string
+ */
+ size_type
+ size() const noexcept
+ {
+ return dn_;
+ }
+
+ /** Return true the string is empty
+ */
+ bool
+ empty() const noexcept
+ {
+ return n_ == 0;
+ }
+
+ /** Copy a decoded substring to another character string
+
+ This function copies a substring to the
+ character array pointed to by the
+ destination char string, where `rcount`
+ is the smaller of `count` and
+ `size() - pos`.
+
+ @par Exception Safety
+ Strong guarantee.
+ Exceptions thrown on invalid positions.
+
+ @par Preconditions
+
+ @code
+ pos > size()
+ @endcode
+
+ @throw std::out_of_range `pos > size()`
+
+ @param dest pointer to the destination character string
+ @param count requested substring length
+ @param pos position of the first character
+
+ @return Number of characters copied
+ */
+ BOOST_URL_DECL
+ size_type
+ copy(char* dest,
+ size_type count,
+ size_type pos = 0) const;
+
+ /** Return the result of comparing to another string
+
+ The length of the sequences to compare is the smaller of
+ `size()` and `other.size()`.
+
+ The function compares the two strings as if by calling
+ `char_traits::compare(to_string().data(), v.data(), rlen)`.
+ This means the comparison is performed with
+ percent-decoding applied to the current string.
+
+ @param other string to compare
+
+ @return Negative value if this string is less than the other
+ character sequence, zero if the both character sequences are
+ equal, positive value if this string is greater than the other
+ character sequence
+ */
+ BOOST_URL_DECL
+ int
+ compare(string_view other) const noexcept;
+
+ /** Return the result of comparing to another string
+
+ The length of the sequences to compare is the smaller of
+ `size()` and `other.size()`.
+
+ The function compares the two strings as if by calling
+ `char_traits::compare(to_string().data(), v.to_string().data(), rlen)`.
+ This means the comparison is performed with
+ percent-decoding applied to the current string.
+
+ @param other string to compare
+
+ @return Negative value if this string is less than the other
+ character sequence, zero if the both character sequences are
+ equal, positive value if this string is greater than the other
+ character sequence
+ */
+ BOOST_URL_DECL
+ int
+ compare(pct_encoded_view other) const noexcept;
+
+ /** Returns a string with the decoded data
+ */
+ std::string
+ to_string() const
+ {
+ std::string r;
+ assign_to(r);
+ return r;
+ }
+
+ /** Assigns the decoded characters to a container
+
+ This function allows the caller to recycle
+ the input string.
+
+ @return A string representing the
+ entire contents of the decoded range.
+ */
+ template
+ MutableString&
+ assign_to(MutableString& s) const;
+
+ /** Appends the decoded characters to a container
+
+ This function allows the caller to recycle
+ the input string.
+
+ @return A string representing the
+ entire contents of the decoded range.
+ */
+ template
+ MutableString&
+ append_to(MutableString& s) const;
+
+ /** Return true if two strings are equal
+ */
+ friend
+ bool
+ operator==(
+ pct_encoded_view s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s0.compare(s1) == 0;
+ }
+
+ /** Return true if two strings are not equal
+ */
+ friend
+ bool
+ operator!=(
+ pct_encoded_view s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s0.compare(s1) != 0;
+ }
+
+ /** Return true if s0 is less than s1
+ */
+ friend
+ bool
+ operator<(
+ pct_encoded_view s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s0.compare(s1) < 0;
+ }
+
+ /** Return true if s0 is less or equal to s1
+ */
+ friend
+ bool
+ operator<=(
+ pct_encoded_view s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s0.compare(s1) <= 0;
+ }
+
+ /** Return true if s0 is greater than s1
+ */
+ friend
+ bool
+ operator>(
+ pct_encoded_view s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s0.compare(s1) > 0;
+ }
+
+ /** Return true if s0 is greater or equal to s1
+ */
+ friend
+ bool
+ operator>=(
+ pct_encoded_view s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s0.compare(s1) >= 0;
+ }
+
+ /** Return true if two strings are equal
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator==(
+ pct_encoded_view s0,
+ String const& s1) noexcept
+ {
+ return s0.compare(string_view(s1)) == 0;
+ }
+
+ /** Return true if two strings are not equal
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator!=(
+ pct_encoded_view s0,
+ String const& s1) noexcept
+ {
+ return s0.compare(string_view(s1)) != 0;
+ }
+
+ /** Return true if s0 is less than s1
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator<(
+ pct_encoded_view s0,
+ String const& s1) noexcept
+ {
+ return s0.compare(string_view(s1)) < 0;
+ }
+
+ /** Return true if s0 is less or equal to s1
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator<=(
+ pct_encoded_view s0,
+ String const& s1) noexcept
+ {
+ return s0.compare(string_view(s1)) <= 0;
+ }
+
+ /** Return true if s0 is greater than s1
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator>(
+ pct_encoded_view s0,
+ String const& s1) noexcept
+ {
+ return s0.compare(string_view(s1)) > 0;
+ }
+
+ /** Return true if s0 is greater or equal to s1
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator>=(
+ pct_encoded_view s0,
+ String const& s1) noexcept
+ {
+ return s0.compare(string_view(s1)) >= 0;
+ }
+
+ /** Return true if two strings are equal
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator==(
+ String const& s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s1.compare(string_view(s0)) == 0;
+ }
+
+ /** Return true if two strings are not equal
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator!=(
+ String const& s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s1.compare(string_view(s0)) != 0;
+ }
+
+ /** Return true if s0 is less than s1
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator<(
+ String const& s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s1.compare(string_view(s0)) > 0;
+ }
+
+ /** Return true if s0 is less or equal to s1
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator<=(
+ String const& s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s1.compare(string_view(s0)) >= 0;
+ }
+
+ /** Return true if s0 is greater than s1
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator>(
+ String const& s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s1.compare(string_view(s0)) < 0;
+ }
+
+ /** Return true if s0 is greater or equal to s1
+
+ @par Constraints
+
+ @code
+ std::is_convertible< String, string_view >
+ @endcode
+
+ */
+ template
+ friend
+#ifndef BOOST_URL_DOCS
+ typename std::enable_if<
+ std::is_convertible::value &&
+ !std::is_same::type, pct_encoded_view>::value,
+ bool
+ >::type
+#else
+ bool
+#endif
+ operator>=(
+ String const& s0,
+ pct_encoded_view s1) noexcept
+ {
+ return s1.compare(string_view(s0)) <= 0;
+ }
+
+ /** Format the decoded view to the output stream
+
+ This function serializes the decoded view
+ to the output stream.
+
+ @return A reference to the output stream, for chaining
+
+ @param os The output stream to write to
+
+ @param s The decoded view to write
+ */
+ friend BOOST_URL_DECL
+ std::ostream&
+ operator<<(
+ std::ostream& os,
+ pct_encoded_view const& s);
+};
+
+} // urls
+} // boost
+
+#include
+
+#endif
diff --git a/include/boost/url/src.hpp b/include/boost/url/src.hpp
index 84877eb2b..954009fb0 100644
--- a/include/boost/url/src.hpp
+++ b/include/boost/url/src.hpp
@@ -49,6 +49,7 @@ in a translation unit of the program.
#include
#include
#include
+#include
#include
#include
#include
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 7be6e622f..7b5248362 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -18,6 +18,7 @@ set(BOOST_URL_TESTS_FILES
Jamfile
test_rule.hpp
authority_view.cpp
+ const_string.cpp
error.cpp
error_code.cpp
grammar.cpp
@@ -28,6 +29,7 @@ set(BOOST_URL_TESTS_FILES
params_encoded.cpp
params_encoded_view.cpp
params_view.cpp
+ pct_encoded_view.cpp
pct_encoding.cpp
pct_encoding_types.cpp
query_param.cpp
@@ -39,7 +41,6 @@ set(BOOST_URL_TESTS_FILES
snippets.cpp
static_pool.cpp
static_url.cpp
- const_string.cpp
string_view.cpp
url.cpp
url_view.cpp
diff --git a/test/unit/Jamfile b/test/unit/Jamfile
index 6dea12c28..7f9dd1f29 100644
--- a/test/unit/Jamfile
+++ b/test/unit/Jamfile
@@ -22,6 +22,7 @@ project
local SOURCES =
../../extra/test_main.cpp
authority_view.cpp
+ const_string.cpp
error.cpp
error_code.cpp
grammar.cpp
@@ -32,6 +33,7 @@ local SOURCES =
params_encoded.cpp
params_encoded_view.cpp
params_view.cpp
+ pct_encoded_view.cpp
pct_encoding.cpp
pct_encoding_types.cpp
query_param.cpp
@@ -43,7 +45,6 @@ local SOURCES =
snippets.cpp
static_pool.cpp
static_url.cpp
- const_string.cpp
string_view.cpp
url.cpp
url_view.cpp
diff --git a/test/unit/pct_encoded_view.cpp b/test/unit/pct_encoded_view.cpp
new file mode 100644
index 000000000..7a96e874b
--- /dev/null
+++ b/test/unit/pct_encoded_view.cpp
@@ -0,0 +1,477 @@
+//
+// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// Official repository: https://github.com/CPPAlliance/url
+//
+
+// Test that header file is self-contained.
+#include
+#include
+
+#include
+
+#include "test_suite.hpp"
+
+namespace boost {
+namespace urls {
+
+struct pct_encoded_view_test
+{
+ string_view str = "a%20uri+test";
+ string_view dec_str = "a uri test";
+ string_view no_plus_dec_str = "a uri+test";
+ const std::size_t dn = 10;
+ pct_decode_opts no_plus_opt;
+
+ pct_encoded_view_test()
+ {
+ no_plus_opt.plus_to_space = false;
+ }
+
+ void
+ testDecodedView()
+ {
+ // pct_encoded_view()
+ {
+ pct_encoded_view s;
+ BOOST_TEST_EQ(s, "");
+ BOOST_TEST_EQ(s.size(), 0u);
+ BOOST_TEST_EQ(s.encoded().size(), 0u);
+ }
+
+ // pct_encoded_view(bool plus_to_space)
+ {
+ pct_encoded_view s(no_plus_opt);
+ BOOST_TEST_EQ(s, "");
+ BOOST_TEST_EQ(s.size(), 0u);
+ BOOST_TEST_EQ(s.encoded().size(), 0u);
+ }
+
+ // pct_encoded_view(char const*)
+ {
+ pct_encoded_view s(str.data());
+ BOOST_TEST_EQ(s, dec_str);
+ BOOST_TEST_EQ(s.size(), dn);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+
+ // pct_encoded_view(char const*, bool plus_to_space)
+ {
+ pct_encoded_view
+ s(str.data(), no_plus_opt);
+ BOOST_TEST_EQ(s, no_plus_dec_str);
+ BOOST_TEST_EQ(s.size(), dn);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+
+ // pct_encoded_view(string_view)
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s, dec_str);
+ BOOST_TEST_EQ(s.size(), dn);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+
+ // pct_encoded_view(string_view, bool plus_to_space)
+ {
+ pct_encoded_view s(str, no_plus_opt);
+ BOOST_TEST_EQ(s, no_plus_dec_str);
+ BOOST_TEST_EQ(s.size(), dn);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+
+#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
+ // pct_encoded_view(string_view)
+ {
+ std::string_view std_str = str;
+ pct_encoded_view s(std_str);
+ BOOST_TEST_EQ(s, dec_str);
+ BOOST_TEST_EQ(s.size(), dn);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+
+ // pct_encoded_view(string_view, bool plus_to_space)
+ {
+ std::string_view std_str = str;
+ pct_encoded_view s(std_str, no_plus_opt);
+ BOOST_TEST_EQ(s, no_plus_dec_str);
+ BOOST_TEST_EQ(s.size(), dn);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+#endif
+
+ // pct_encoded_view(string_view)
+ {
+ std::string ss(str);
+ pct_encoded_view s(ss);
+ BOOST_TEST_EQ(s, dec_str);
+ BOOST_TEST_EQ(s.size(), dn);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+
+ // pct_encoded_view(string_view, bool plus_to_space)
+ {
+ std::string ss(str);
+ pct_encoded_view s(ss, no_plus_opt);
+ BOOST_TEST_EQ(s, no_plus_dec_str);
+ BOOST_TEST_EQ(s.size(), dn);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+ }
+
+ void
+ testIter()
+ {
+ // begin()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(*s.begin(), s.front());
+ BOOST_TEST_NE(s.begin(),
+ pct_encoded_view::iterator{});
+ }
+
+ // end()
+ {
+ pct_encoded_view s(str);
+ auto l = s.end();
+ --l;
+ BOOST_TEST_EQ(*l, s.back());
+ BOOST_TEST_NE(l,
+ pct_encoded_view::iterator{});
+ }
+ }
+
+ void
+ testAccessors()
+ {
+ // front()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s.front(), 'a');
+ }
+
+ // back()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s.back(), 't');
+ }
+
+ // encoded().data()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s.encoded().data(), str.data());
+ }
+ }
+
+ void
+ testObservers()
+ {
+ // size()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s.size(), dn);
+ }
+
+ // encoded().size()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s.encoded().size(), str.size());
+ }
+
+ // encoded().length()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s.encoded().length(), str.size());
+ }
+
+ // encoded().max_size()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_GT(s.encoded().max_size(), 0u);
+ }
+
+ // empty()
+ {
+ pct_encoded_view s;
+ BOOST_TEST(s.empty());
+
+ pct_encoded_view s2(str);
+ BOOST_TEST_NOT(s2.empty());
+ }
+ }
+
+ void
+ testCopy()
+ {
+ // copy()
+ {
+ pct_encoded_view s(str);
+ std::string out(s.size(), ' ');
+ s.copy(&out[0], s.size());
+ BOOST_TEST_EQ(s, dec_str);
+ }
+ }
+
+ void
+ testCompare()
+ {
+ // compare()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s.compare(dec_str), 0);
+ BOOST_TEST_EQ(s.compare("a a"), 1);
+ BOOST_TEST_EQ(s.compare("a z"), -1);
+ std::string bs = "z";
+ BOOST_TEST_EQ(s.compare(bs), -1);
+ }
+
+ // operators
+ {
+ pct_encoded_view s(str);
+
+ // pct_encoded_view
+ {
+ pct_encoded_view s0(str);
+ pct_encoded_view s1("a%20tri+test");
+ pct_encoded_view s2("a%20vri+test");
+ BOOST_TEST(s == s0);
+ BOOST_TEST_NOT(s == s1);
+ BOOST_TEST(s != s2);
+ BOOST_TEST_NOT(s != s0);
+ BOOST_TEST(s < s2);
+ BOOST_TEST_NOT(s < s0);
+ BOOST_TEST(s <= s2);
+ BOOST_TEST(s <= s0);
+ BOOST_TEST(s > s1);
+ BOOST_TEST_NOT(s > s0);
+ BOOST_TEST(s >= s1);
+ BOOST_TEST(s >= s0);
+ }
+
+ // string_view
+ {
+ string_view str0(dec_str);
+ string_view str1("a tri test");
+ string_view str2("a vri test");
+ BOOST_TEST(s == str0);
+ BOOST_TEST_NOT(s == str1);
+ BOOST_TEST(s != str2);
+ BOOST_TEST_NOT(s != str0);
+ BOOST_TEST(s < str2);
+ BOOST_TEST_NOT(s < str0);
+ BOOST_TEST(s <= str2);
+ BOOST_TEST(s <= str0);
+ BOOST_TEST(s > str1);
+ BOOST_TEST_NOT(s > str0);
+ BOOST_TEST(s >= str1);
+ BOOST_TEST(s >= str0);
+ }
+
+ // string
+ {
+ std::string bstr0(dec_str);
+ std::string bstr1("a tri test");
+ std::string bstr2("a vri test");
+ BOOST_TEST(s == bstr0);
+ BOOST_TEST_NOT(s == bstr1);
+ BOOST_TEST(s != bstr2);
+ BOOST_TEST_NOT(s != bstr0);
+ BOOST_TEST(s < bstr2);
+ BOOST_TEST_NOT(s < bstr0);
+ BOOST_TEST(s <= bstr2);
+ BOOST_TEST(s <= bstr0);
+ BOOST_TEST(s > bstr1);
+ BOOST_TEST_NOT(s > bstr0);
+ BOOST_TEST(s >= bstr1);
+ BOOST_TEST(s >= bstr0);
+ }
+
+
+ // string literals
+ {
+ BOOST_TEST(s == "a uri test");
+ BOOST_TEST_NOT(s == "a tri test");
+ BOOST_TEST(s != "a vri test");
+ BOOST_TEST_NOT(s != "a uri test");
+ BOOST_TEST(s < "a vri test");
+ BOOST_TEST_NOT(s < "a uri test");
+ BOOST_TEST(s <= "a vri test");
+ BOOST_TEST(s <= "a uri test");
+ BOOST_TEST(s > "a tri test");
+ BOOST_TEST_NOT(s > "a uri test");
+ BOOST_TEST(s >= "a tri test");
+ BOOST_TEST(s >= "a uri test");
+ }
+
+ }
+ }
+
+ void
+ testConversion()
+ {
+ // to_string()
+ {
+ pct_encoded_view s(str);
+ BOOST_TEST_EQ(s.to_string(), dec_str);
+ }
+
+ // append_to()
+ {
+ pct_encoded_view s(str);
+ std::string o = "init ";
+ s.append_to(o);
+
+ std::string exp = std::string("init ");
+ exp.append(dec_str.data(), dec_str.size());
+
+ BOOST_TEST_EQ(o, exp);
+ }
+
+ // assign_to()
+ {
+ pct_encoded_view s(str);
+ std::string o = "init ";
+ s.assign_to(o);
+ BOOST_TEST_EQ(o, dec_str);
+ }
+
+ // pass it to a function taking a string_view
+ {
+ auto f = [this](string_view sv)
+ {
+ BOOST_TEST(sv == dec_str);
+ };
+ pct_encoded_view s(str);
+ f(s.to_string());
+ }
+
+ // pass it to a function taking a char*
+ {
+ auto f = [this](char const* sv)
+ {
+ BOOST_TEST(sv == dec_str);
+ };
+ pct_encoded_view s(str);
+ f(s.to_string().c_str());
+ }
+ }
+
+ void
+ testStream()
+ {
+ // operator<<
+ {
+ std::stringstream ss;
+ pct_encoded_view s(str);
+ ss << s;
+ BOOST_TEST_EQ(ss.str(), dec_str);
+ }
+ }
+
+ void
+ testPR127Cases()
+ {
+ {
+ std::stringstream ss;
+ urls::pct_encoded_view ds("test+string");
+ // no warning about object slicing
+ ss << ds;
+ }
+
+ {
+ auto break_stuff =
+ [this](urls::string_view const& a) {
+ urls::string_view b{};
+ b = a;
+ BOOST_TEST_EQ(b.size(), dn);
+ };
+ break_stuff(pct_encoded_view{str}.to_string());
+ }
+
+ {
+#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
+ struct A
+ {
+ A( std::string_view s)
+ {
+ boost::ignore_unused(s);
+ }
+
+ A( std::string_view s, std::size_t dn )
+ {
+ BOOST_TEST_EQ(s.size(), dn);
+ boost::ignore_unused(s, dn);
+ }
+ };
+ A a1(pct_encoded_view{str}.to_string(), dn);
+ A a2(std::string_view(pct_encoded_view{str}.to_string()), dn);
+ A a3 = std::string_view(pct_encoded_view{str}.to_string());
+ // https://en.cppreference.com/w/cpp/language/copy_initialization#Notes
+ // A a4 = pct_encoded_view{str}.to_string();
+ boost::ignore_unused(a1, a2, a3);
+#endif
+ struct B
+ {
+ B( urls::string_view s)
+ {
+ boost::ignore_unused(s);
+ }
+
+ B( urls::string_view s, std::size_t dn )
+ {
+ BOOST_TEST_EQ(s.size(), dn);
+ boost::ignore_unused(s, dn);
+ }
+ };
+ B b1(pct_encoded_view{str}.to_string(), dn);
+ B b2(string_view(
+ pct_encoded_view{str}.to_string()), dn);
+ B b3 = string_view(
+ pct_encoded_view{str}.to_string());
+ // https://en.cppreference.com/w/cpp/language/copy_initialization#Notes
+ // B b4 = pct_encoded_view{str}.to_string();
+ boost::ignore_unused(b1, b2, b3);
+ }
+
+ {
+#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
+ auto f1 = []( std::string_view ) {};
+ f1(pct_encoded_view{str}.to_string());
+#endif
+ auto f2 = []( urls::string_view ) {};
+ f2(pct_encoded_view{str}.to_string());
+
+ auto f3 = []( std::string const& ) {};
+ f3(pct_encoded_view{str}.to_string());
+
+ auto f4 = []( std::basic_string,
+ std::allocator> const&) {};
+ f4(pct_encoded_view{str}.to_string());
+ }
+ }
+
+ void
+ run()
+ {
+ testDecodedView();
+ testIter();
+ testAccessors();
+ testObservers();
+ testCopy();
+ testCompare();
+ testConversion();
+ testStream();
+ testPR127Cases();
+ }
+};
+
+TEST_SUITE(
+ pct_encoded_view_test,
+ "boost.url.pct_encoded_view");
+
+} // urls
+} // boost
diff --git a/test/unit/snippets.cpp b/test/unit/snippets.cpp
index 6a4e72606..c6e7824f9 100644
--- a/test/unit/snippets.cpp
+++ b/test/unit/snippets.cpp
@@ -931,6 +931,17 @@ struct CharSet
};
//]
+//[snippet_mutable_string_exemplar
+struct MutableString
+{
+ template< class InputIt >
+ void assign( InputIt first, InputIt last );
+
+ template< class InputIt >
+ void append( InputIt first, InputIt last );
+};
+//]
+
void
grammar_charset()