2968. Inconsistencies between basic_string reserve and vector/unordered_map/unordered_set reserve functions

Section: 27.4.3.5 [string.capacity] Status: Resolved Submitter: Andrew Luo Opened: 2017-05-30 Last modified: 2020-09-06

Priority: 3

View all other issues in [string.capacity].

View all issues with Resolved status.

Discussion:

According to 27.4.3.5 [string.capacity] paragraph 11:

-11- Effects: After reserve(), capacity() is greater or equal to the argument of reserve. [Note: Calling reserve() with a res_arg argument less than capacity() is in effect a non-binding shrink request. A call with res_arg <= size() is in effect a non-binding shrink-to-fit request. — end note]

A call to basic_string's reserve function with res_arg <= size() is taken as a non-binding request to shrink the capacity, whereas for vector (and similarly for unordered_map and unordered_set) according to 23.3.11.3 [vector.capacity] p3:

-3- Effects: A directive that informs a vector of a planned change in size, so that it can manage the storage allocation accordingly. After reserve(), capacity() is greater or equal to the argument of reserve if reallocation happens; and equal to the previous value of capacity() otherwise. Reallocation happens at this point if and only if the current capacity is less than the argument of reserve(). If an exception is thrown other than by the move constructor of a non-CopyInsertable type, there are no effects.

The problem here is that the different behavior makes it that writing template code where the template argument type is a container type (for example std::string or std::vector<char>) calls to reserve can have different meaning depending on which container type the template is instantiated with. It might be a minor issue but it would be nice to fix the inconsistency. I ran into an issue around this when I was porting code from MSVC++ to G++ (For basic_string, MSVC++'s STL implementation, based on Dinkumware, ignores the call if res_arg < capacity() whereas GCC's STL implementation, libstdc++ will actually shrink the string. For the code I wrote this caused a huge performance issue since we were reallocating the entire string with every call to reserve. Of course we could have worked around it by doing the res_arg < capacity() check ourselves, but I think this inconsistency in the standard isn't desirable).

My proposal is to change 27.4.3.5 [string.capacity] paragraph 11 to read:

-11- Effects: After reserve(), capacity() is greater or equal to the argument of reserve if reallocation happens; and equal to the previous value of capacity() otherwise. Reallocation happens at this point if and only if the current capacity is less than the argument of reserve().

I realize that this causes the basic_string::reserve to no longer have the secondary property of shrinking, but this is what shrink_to_fit is for.

[2017-07 Toronto Monday issue prioritization]

Priority 3; status to LEWG

[2018-3-17 Resolved by P0966, which was adopted in Jacksonville.]

Proposed resolution:

This wording is relative to N4659.

  1. Edit 27.4.3.5 [string.capacity] as indicated:

    void reserve(size_type res_arg=0);
    

    -10- The member function reserve() is a directive that informs a basic_string object of a planned change in size, so that it can manage the storage allocation accordingly.

    -11- Effects: After reserve(), capacity() is greater or equal to the argument of reserve, if reallocation happens; and equal to the previous value of capacity() otherwise. Reallocation happens at this point if and only if the current capacity is less than the argument of reserve(). [Note: Calling reserve() with a res_arg argument less than capacity() is in effect a non-binding shrink request. A call with res_arg <= size() is in effect a non-binding shrink-to-fit request. — end note]

    -12- Throws: length_error if res_arg > max_size().