Revised 2025-12-22 at 14:28:09 UTC

Tentative Issues


3831(i). Two-digit formatting of negative year is ambiguous

Section: 30.12 [time.format], 30.13 [time.parse] Status: Tentatively Ready Submitter: Matt Stephanson Opened: 2022-11-18 Last modified: 2025-12-04

Priority: 3

View other active issues in [time.format].

View all other issues in [time.format].

View all issues with Tentatively Ready status.

Discussion:

An issue has been identified regarding the two-digit formatting of negative years according to Table [tab:time.format.spec] (30.12 [time.format]):

cout << format("{:%y} ", 1976y)  // "76"
     << format("{:%y}", -1976y); // also "76"?

The relevant wording is

The last two decimal digits of the year. If the result is a single digit it is prefixed by 0. The modified command %Oy produces the locale's alternative representation. The modified command %Ey produces the locale's alternative representation of offset from %EC (year only).

MSVC STL treats the regular modified form symmetrically. Just as %Ey is the offset from %EC, so %y is the offset from %C, which is itself "[t]he year divided by 100 using floored division." (emphasis added). Because -1976 is the 24th year of the -20th century, the above code will print "76 24" using MSVC STL. However, many users expect, and libc++ gives, a result based on the literal wording, "76 76".

IEEE 1003.1-2008 strftime expects the century to be nonnegative, but the glibc implementation prints 24 for -1976. My own opinion is that this is the better result, because it consistently interprets %C and %y as the quotient and remainder of floored division by 100.

Howard Hinnant, coauthor of the original 30.12 [time.format] wording in P0355 adds:

On the motivation for this design it is important to remember a few things:

This leaves how to represent negative years with %y. I can think of 3 options:

  1. Use the last two digits without negating: -1976 → 76.

  2. Use the last two digits and negate it: -1976 → -76.

  3. Use floored modulus arithmetic: -1976 → 24.

The algorithm to convert %C and %y into a year is not important to the client because these are both strings, not integers. The client will do it with parse, not 100*C + y.

I discounted solution 3 as not sufficiently obvious. If the output for -1976 was 23, the human reader wouldn't immediately know that this is off by 1. The reader is expecting the POSIX spec:

the last two digits of the year as a decimal number [00,99].

24 just doesn't cut it.

That leaves solution 1 or 2. I discounted solution 2 because having the negative in 2 places (the %C and %y) seemed overly complicated and more error prone. The negative sign need only be in one place, and it has to be in %C to prevent ambiguity.

That leaves solution 1. I believe this is the solution for an extension of the POSIX spec to negative years with the property of least surprise to the client. The only surprise is in %C, not %y, and the surprise in %C seems unavoidable.

[2022-11-30; Reflector poll]

Set priority to 3 after reflector poll.

A few votes for priority 2. Might need to go to LEWG.

Previous resolution [SUPERSEDED]:

This wording is relative to N4917.

[Drafting Note: Two mutually exclusive options are prepared, depicted below by Option A and Option B, respectively.]

Option A: This is Howard Hinnant's choice (3)

  1. Modify 30.12 [time.format], Table [tab:time.format.spec] as indicated:

    Table 102 — Meaning of conversion specifiers [tab:time.format.spec]
    Specifier Replacement
    […]
    %y The last two decimal digits of the yearremainder after dividing the year by 100 using floored division.
    If the result is a single digit it is prefixed by 0.
    The modified command %Oy produces the locale's alternative representation. The
    modified command %Ey produces the locale's alternative representation of offset from
    %EC (year only).
    […]
  2. Modify 30.13 [time.parse], Table [tab:time.parse.spec] as indicated:

    Table 103 — Meaning of parse flags [tab:time.parse.spec]
    Flag Parsed value
    […]
    %y The last two decimal digits of the yearremainder after dividing the year by 100 using floored division.
    If the century is not otherwise specified (e.g.
    with %C), values in the range [69, 99] are presumed to refer to the years 1969 to 1999,
    and values in the range [00, 68] are presumed to refer to the years 2000 to 2068. The
    modified command %N y specifies the maximum number of characters to read. If N is
    not specified, the default is 2. Leading zeroes are permitted but not required. The
    modified commands %Ey and %Oy interpret the locale's alternative representation.
    […]

Option B: This is Howard Hinnant's choice (1)

  1. Modify 30.12 [time.format], Table [tab:time.format.spec] as indicated:

    Table 102 — Meaning of conversion specifiers [tab:time.format.spec]
    Specifier Replacement
    […]
    %y The last two decimal digits of the year, regardless of the sign of the year.
    If the result is a single digit it is prefixed by 0.
    The modified command %Oy produces the locale's alternative representation. The
    modified command %Ey produces the locale's alternative representation of offset from
    %EC (year only).
    [Example ?: cout << format("{:%C %y}", -1976y); prints -20 76. — end example]
    […]
  2. Modify 30.13 [time.parse], Table [tab:time.parse.spec] as indicated:

    Table 103 — Meaning of parse flags [tab:time.parse.spec]
    Flag Parsed value
    […]
    %y The last two decimal digits of the year, regardless of the sign of the year.
    If the century is not otherwise specified (e.g.
    with %C), values in the range [69, 99] are presumed to refer to the years 1969 to 1999,
    and values in the range [00, 68] are presumed to refer to the years 2000 to 2068. The
    modified command %N y specifies the maximum number of characters to read. If N is
    not specified, the default is 2. Leading zeroes are permitted but not required. The
    modified commands %Ey and %Oy interpret the locale's alternative representation.
    [Example ?: year y; istringstream{"-20 76"} >> parse("%3C %y", y); results in
    y == -1976y. — end example]
    […]

[2025-10-17; Jonathan provides updated wording using Option B]

[2025-12-04; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 30.12 [time.format], Table [tab:time.format.spec] as indicated:

    Table 133 — Meaning of conversion specifiers [tab:time.format.spec]
    Specifier Replacement
    […]
    %y The last two decimal digits of the year, regardless of the sign of the year.
    If the result is a single digit it is prefixed by 0.
    The modified command %Oy produces the locale's alternative representation. The
    modified command %Ey produces the locale's alternative representation of offset from
    %EC (year only).
    [Example ?: cout << format("{:%C %y}", -1976y); prints -20 76. — end example]
    […]
  2. Modify 30.13 [time.parse], Table [tab:time.parse.spec] as indicated:

    Table 103 — Meaning of parse flags [tab:time.parse.spec]
    Flag Parsed value
    […]
    %y The last two decimal digits of the year, regardless of the sign of the year.
    If the century is not otherwise specified (e.g.
    with %C), values in the range [69, 99] are presumed to refer to the years 1969 to 1999,
    and values in the range [00, 68] are presumed to refer to the years 2000 to 2068. The
    modified command %N y specifies the maximum number of characters to read. If N is
    not specified, the default is 2. Leading zeroes are permitted but not required. The
    modified commands %Ey and %Oy interpret the locale's alternative representation.
    [Example ?: year y; istringstream{"-20 76"} >> parse("%3C %y", y); results in
    y == -1976y. — end example]
    […]

4259(i). P1148R0 changed the return values of searching functions of std::basic_string on some platforms

Section: 27.4.3.8.2 [string.find] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-05-05 Last modified: 2025-12-04

Priority: 3

View other active issues in [string.find].

View all other issues in [string.find].

View all issues with Tentatively Ready status.

Discussion:

P1148R0 respecified the searching functions of std::basic_string to return corresponding string view type's npos member constant (equal to std::size_t(-1)), converted to the string type S's member S::size_type, when the search fails. Before the change, S::npos (equal to S::size_type(-1)) was returned on failure.

On platforms where std::size_t isn't the widest unsigned integer type (e.g. on usual 32-bit platforms), the return value can change. Because there can be an allocator with a wider size_type, and when the basic_string type S uses such an allocator, S::size_type is specified to be that type, which in turn makes S::size_type(std::size_t(-1)) not equal to S::size_type(-1).

Do we want to restore the old return values?

Previous resolution [SUPERSEDED]:

This wording is relative to N5008.

  1. Modify 27.4.3.8.2 [string.find] as indicated:

    template<class T>
      constexpr size_type find(const T& t, size_type pos = 0) const noexcept(see below);
    […]
    template<class T>
      constexpr size_type find_last_not_of(const T& t, size_type pos = npos) const noexcept(see below);
    

    -2- Constraints: […]

    -3- Effects: Let G be the name of the function. Equivalent to:

    basic_string_view<charT, traits> s = *this, sv = t;
    return s.G(sv, pos);
    if (auto result = s.G(sv, pos); result == size_t(-1))
      return npos;
    else
      return result;
    

[2025-06-10, reflector discussion]

During reflector discussion of this issue there was a preference to adjust the proposed wording to use s.npos instead of size_t(-1).

[2025-10-21; Reflector poll.]

Set priority to 3 after reflector poll.

[2025-12-04; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 27.4.3.8.2 [string.find] as indicated:

    template<class T>
      constexpr size_type find(const T& t, size_type pos = 0) const noexcept(see below);
    […]
    template<class T>
      constexpr size_type find_last_not_of(const T& t, size_type pos = npos) const noexcept(see below);
    

    -2- Constraints: […]

    -3- Effects: Let G be the name of the function. Equivalent to:

    basic_string_view<charT, traits> s = *this, sv = t;
    return s.G(sv, pos);
    if (auto result = s.G(sv, pos); result == s.npos)
      return npos;
    else
      return result;
    

4324(i). unique_ptr<void>::operator* is not SFINAE-friendly

Section: 20.3.1.3.5 [unique.ptr.single.observers] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2025-08-24 Last modified: 2025-12-04

Priority: 3

View other active issues in [unique.ptr.single.observers].

View all other issues in [unique.ptr.single.observers].

View all issues with Tentatively Ready status.

Discussion:

LWG 2762(i) added a conditional noexcept specification to unique_ptr::operator* to make it consistent with shared_ptr::operator*:

constexpr add_lvalue_reference_t<T> operator*() const noexcept(noexcept(*declval<pointer>()));

This unexpectedly makes unique_ptr<void>::operator* no longer SFINAE-friendly, for example:

#include <memory>

template<class T> concept dereferenceable = requires(T& t) { *t; };

static_assert( dereferenceable<int *>);
static_assert(!dereferenceable<void*>);

static_assert( dereferenceable<std::shared_ptr<int >>);
static_assert(!dereferenceable<std::shared_ptr<void>>);

static_assert( dereferenceable<std::unique_ptr<int >>);
static_assert( dereferenceable<std::unique_ptr<void>>); // hard error

Given that the standard intends for operator* of shared_ptr and unique_ptr to be SFINAE-friendly based on 20.3.2.2.6 [util.smartptr.shared.obs], regardless of the value of static_assert, it is reasonable to assume that there should be no hard error here.

Previous resolution [SUPERSEDED]:

This wording is relative to N5014.

  1. Modify 20.3.1.3.5 [unique.ptr.single.observers] as indicated:

    constexpr add_lvalue_reference_t<T> operator*() const noexcept(noexcept(*declval<pointer>())see below);
    

    -1- Mandates: reference_converts_from_temporary_v<add_lvalue_reference_t<T>, decltype(*declval<pointer>())> is false.

    -2- Preconditions: get() != nullptr is true.

    -3- Returns: *get().

    -?- Remarks:: The exception specification is equivalent to:

    !requires { *declval<pointer>(); } || requires { { *declval<pointer>() } noexcept; }
    

[2025-08-26; Reflector discussion]

During reflector triaging it had been pointed out that a better solution would be to constrain the operator* directly. The proposed wording has been updated to that effect.

[2025-10-22; Reflector poll.]

Set priority to 3 after reflector poll.

[2025-12-04; Reflector poll.]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 20.3.1.3.5 [unique.ptr.single.observers] as indicated:

    constexpr add_lvalue_reference_t<T> operator*() const noexcept(noexcept(*declval<pointer>()));
    

    -?- Constraints: *declval<pointer>() is a well-formed expression.

    -1- Mandates: reference_converts_from_temporary_v<add_lvalue_reference_t<T>, decltype(*declval<pointer>())> is false.

    -2- Preconditions: get() != nullptr is true.

    -3- Returns: *get().


4460(i). Missing Throws: for last variant constructor

Section: 22.6.3.2 [variant.ctor] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-11-07 Last modified: 2025-12-04

Priority: Not Prioritized

View other active issues in [variant.ctor].

View all other issues in [variant.ctor].

View all issues with Tentatively Ready status.

Discussion:

All variant constructors except the last one have a Throws: element saying what they're allowed to throw.

This originates from an editorial pull request, where the submitter said:

"It looks like this defect is an artifact of a change between P0088R0 and P0088R1. Note how in R0 neither one of the emplaced_type_t/emplaced_index_t (as they were then called) + initializer_list constructors have a throws clause. In R1 only one of them gained it."

Previous resolution [SUPERSEDED]:

This wording is relative to N5014.

  1. Modify 22.6.3.2 [variant.ctor], as indicated:

    template<size_t I, class U, class... Args>
      constexpr explicit variant(in_place_index_t<I>, initializer_list<U> il, Args&&... args);
    

    -35- Constraints:

    1. (35.1) — I is less than sizeof...(Types) and
    2. (35.2) — is_constructible_v<TI, initializer_list<U>&, Args...> is true.

    -36- Effects: Direct-non-list-initializes the contained value of type TI with il, std::forward<Args>(args)....

    -37- Postconditions: index() is I.

    -?- Throws: Any exception thrown by calling the selected constructor of TI.

    -38- Remarks: If TI’s selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

[2025-11-11; Jonathan provides improved wording]

[2025-12-04; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 22.6.3.2 [variant.ctor], as indicated:

    constexpr variant() noexcept(see below);
    

    -2- Constraints: is_default_constructible_v<T0> is true.

    -3- Effects: Constructs a variant holding a value-initialized value of type T0.

    -4- Postconditions: valueless_by_exception() is false and index() is 0.

    -5- Throws: Any exception thrown by the value-initialization of T0.

    -6- Remarks: […]

    constexpr variant(const variant&);
    

    -7- Effects: If w holds a value, initializes the variant to hold the same alternative as w and direct-initializes the contained value with GET<j>(w), where j is w.index(). Otherwise, initializes the variant to not hold a value.

    -8- Throws: Any exception thrown by direct-initializating any Ti for all i the initialization of the contained value.

    -9- Remarks: […]

    constexpr variant(variant&&) noexcept(see below);
    

    -10- Constraints: is_move_constructible_v<Ti> is true for all i.

    -11- Effects: If w holds a value, initializes the variant to hold the same alternative as w and direct-initializes the contained value with GET<j>(std::move(w)), where j is w.index(). Otherwise, initializes the variant to not hold a value.

    -12- Throws: Any exception thrown by move-constructing any Ti for all i the initialization of the contained value.

    -13- Remarks: […]

    template<class T> constexpr variant(T&&) noexcept(see below);
    

    -14- Let Tj be a type that is determined as follows: build an imaginary function FUN(Ti) for each alternative type Ti for which Ti x[] = {std::forward<T>(t)}; is well-formed for some invented variable x. The overload FUN(Tj) selected by overload resolution for the expression FUN(std::forward<T>(t)) defines the alternative Tj which is the type of the contained value after construction.

    -15- Constraints: […]

    -16- Effects: Initializes *this to hold the alternative type Tj and direct-non-list-initializes the contained value with std::forward<T>(t).

    -17- Postconditions: […]

    -18- Throws: Any exception thrown by the initialization of the selected alternative Tj contained value.

    -19- Remarks: […]

    template<class T, class... Args> constexpr variant(in_place_type_t<T>, Args&&... args);
    

    -20- Constraints: […]

    -21- Effects: Direct-non-list-initializes the contained value of type T with std::forward<Args>(args)....

    -22- Postconditions: […]

    -23- Throws: Any exception thrown by the selected constructor of T the initialization of the contained value.

    -24- Remarks: […]

    template<class T, class U, class... Args>
      constexpr variant(in_place_type_t<T>, initializer_list<U> li, Args&&... args);
    

    -25- Constraints: […]

    -26- Effects: Direct-non-list-initializes the contained value of type T with il, std::forward<Args>(args)....

    -27- Postconditions: […]

    -28- Throws: Any exception thrown by the selected constructor of T the initialization of the contained value.

    -29- Remarks: […]

    template<size_t I, class... Args>
      constexpr explicit variant(in_place_index_t<I>, Args&&... args);
    

    -30- Constraints:

    1. (30.1) — I is less than sizeof...(Types) and
    2. (30.2) — is_constructible_v<TI, Args...> is true.

    -31- Effects: Direct-non-list-initializes the contained value of type TI with std::forward<Args>(args)....

    -32- Postconditions: index() is I.

    -33- Throws: Any exception thrown by the selected constructor of Ti the initialization of the contained value.

    -34- Remarks: If TI’s selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

    template<size_t I, class U, class... Args>
      constexpr explicit variant(in_place_index_t<I>, initializer_list<U> il, Args&&... args);
    

    -35- Constraints:

    1. (35.1) — I is less than sizeof...(Types) and
    2. (35.2) — is_constructible_v<TI, initializer_list<U>&, Args...> is true.

    -36- Effects: Direct-non-list-initializes the contained value of type TI with il, std::forward<Args>(args)....

    -37- Postconditions: index() is I.

    -?- Throws: Any exception thrown by the initialization of the contained value.

    -38- Remarks: If TI’s selected constructor is a constexpr constructor, this constructor is a constexpr constructor.


4467(i). hive::splice can throw bad_alloc

Section: 23.3.9.5 [hive.operations] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-11-07 Last modified: 2025-12-04

Priority: Not Prioritized

View other active issues in [hive.operations].

View all other issues in [hive.operations].

View all issues with Tentatively Ready status.

Discussion:

Moving blocks from the source hive to the destination hive might require reallocating the array of pointers to blocks, so the Throws: element should allow this.

[2025-12-04; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 23.3.9.5 [hive.operations], as indicated:

    void splice(hive& x);
    void splice(hive&& x);
    

    -2- Preconditions: get_allocator() == x.get_allocator() is true.

    -3- Effects: If addressof(x) == this is true, the behavior is erroneous and there are no effects. Otherwise, inserts the contents of x into *this and x becomes empty. Pointers and references to the moved elements of x now refer to those same elements but as members of *this. Iterators referring to the moved elements continue to refer to their elements, but they now behave as iterators into *this, not into x.

    -4- Throws: length_error if any of x's active blocks are not within the bounds of current-limits , as well as any exceptions thrown by the allocator.

    -5- Complexity: Linear in the sum of all element blocks in x plus all element blocks in *this.

    -6- Remarks: Reserved blocks in x are not transferred into *this. If addressof(x) == this is false, invalidates the past-the-end iterator for both x and *this.


4468(i). §[const.wrap.class] "operator decltype(auto)" is ill-formed

Section: 21.3.5 [const.wrap.class] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-11-07 Last modified: 2025-12-04

Priority: Not Prioritized

View all other issues in [const.wrap.class].

View all issues with Tentatively Ready status.

Discussion:

Following the approval of CWG 1670 in Kona 2025, the following declaration in class template constant_wrapper, 21.3.5 [const.wrap.class], is ill-formed:

constexpr operator decltype(auto)() const noexcept { return value; }

[2025-12-04; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 21.3.5 [const.wrap.class], class template constant_wrapper synopsis, as indicated:

    […]
    template<cw-fixed-value X, class>
    struct constant_wrapper : cw-operators {
      static constexpr const auto & value = X.data;
    
      using type = constant_wrapper;
      using value_type = typename decltype(X)::type;
    
      […]
      constexpr operator decltype(autovalue)() const noexcept { return value; }
    };
    

4477(i). Placement operator delete should be constexpr

Section: 17.6.3.4 [new.delete.placement] Status: Tentatively Ready Submitter: Jakub Jelinek Opened: 2025-11-18 Last modified: 2025-11-26

Priority: Not Prioritized

View other active issues in [new.delete.placement].

View all other issues in [new.delete.placement].

View all issues with Tentatively Ready status.

Discussion:

The P2747R2 paper made placement operator new constexpr. At that time constexpr exceptions weren't in C++26, so that was all that was needed. But later on when P3068R5 was voted in, the P2747R2 changes look insufficient. The problem is that when you throw from a constructor during operator new, it invokes placement operator delete. And P2747R2 didn't touch that.

This makes it impossible to handle an exception thrown by a placement new-expression during constant evaluation.

[2025-11-26; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 17.6.2 [new.syn] as indicated:

    constexpr void* operator new  (std::size_t size, void* ptr) noexcept;
    constexpr void* operator new[](std::size_t size, void* ptr) noexcept;
    constexpr void operator delete  (void* ptr, void*) noexcept;
    constexpr void operator delete[](void* ptr, void*) noexcept;
    
  2. Modify 17.6.3.4 [new.delete.placement] as indicated:

    constexpr void* operator new(std::size_t size, void* ptr) noexcept;
    

    […]

    constexpr void* operator new[](std::size_t size, void* ptr) noexcept;
    

    […]

    constexpr void operator delete(void* ptr, void*) noexcept;
    

    -7- Effects: Intentionally performs no action.

    -8- Remarks: Default function called when any part of the initialization in a placement new-expression that invokes the library's non-array placement operator new terminates by throwing an exception (7.6.2.8 [expr.new]).

    constexpr void operator delete[](void* ptr, void*) noexcept;
    

    -9- Effects: Intentionally performs no action.

    -10- Remarks: Default function called when any part of the initialization in a placement new-expression that invokes the library's array placement operator new terminates by throwing an exception (7.6.2.8 [expr.new]).


4480(i). <stdatomic.h> should provide ATOMIC_CHAR8_T_LOCK_FREE

Section: 32.5.12 [stdatomic.h.syn] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-11-21 Last modified: 2025-12-04

Priority: Not Prioritized

View all other issues in [stdatomic.h.syn].

View all issues with Tentatively Ready status.

Discussion:

Currently, <stdatomic.h> is specified to provide atomic_char8_t but not its corresponding ATOMIC_CHAR8_T_LOCK_FREE macro, which is self-inconsistent. Also, given WG14 N2653 added ATOMIC_CHAR8_T_LOCK_FREE to C's <stdatomic.h> in C23, perhaps C++ should do the same thing in the spirit of P3348R4.

[2025-12-04; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 32.5.12 [stdatomic.h.syn], header <stdatomic.h> synopsis, as indicated:

    template<class T>
      using std-atomic = std::atomic<T>; // exposition only
    
    #define _Atomic(T) std-atomic<T>
    
    #define ATOMIC_BOOL_LOCK_FREE see below
    #define ATOMIC_CHAR_LOCK_FREE see below
    #define ATOMIC_CHAR8_T_LOCK_FREE see below
    #define ATOMIC_CHAR16_T_LOCK_FREE see below
    #define ATOMIC_CHAR32_T_LOCK_FREE see below
    #define ATOMIC_WCHAR_T_LOCK_FREE see below
    #define ATOMIC_SHORT_LOCK_FREE see below
    #define ATOMIC_INT_LOCK_FREE see below
    #define ATOMIC_LONG_LOCK_FREE see below
    #define ATOMIC_LLONG_LOCK_FREE see below
    #define ATOMIC_POINTER_LOCK_FREE see below
    
    […]