C++ Standard Library Issues to be moved in [INSERT CURRENT MEETING HERE]

Doc. no. R0165???
Date:

Revised 2018-06-25 at 21:10:08 UTC

Project: Programming Language C++
Reply to: Marshall Clow <lwgchair@gmail.com>

Ready Issues


2183(i). Muddled allocator requirements for match_results constructors

Section: 31.10.1 [re.results.const], 31.10.6 [re.results.all] Status: Ready Submitter: Pete Becker Opened: 2012-08-29 Last modified: 2018-06-11

Priority: 3

View other active issues in [re.results.const].

View all other issues in [re.results.const].

View all issues with Ready status.

Discussion:

31.10.1 [re.results.const] p1 says:

In all match_results constructors, a copy of the Allocator argument shall be used for any memory allocation performed by the constructor or member functions during the lifetime of the object.

There are three constructors:

match_results(const Allocator& = Allocator());
match_results(const match_results& m);
match_results(match_results&& m) noexcept;

The second and third constructors do no have an Allocator argument, so despite the "all match_results constructors", it is not possible to use "the Allocator argument" for the second and third constructors.

The requirements for those two constructors also does not give any guidance. The second constructor has no language about allocators, and the third states that the stored Allocator value is move constructed from m.get_allocator(), but doesn't require using that allocator to allocate memory.

The same basic problem recurs in 31.10.6 [re.results.all], which gives the required return value for get_allocator():

Returns: A copy of the Allocator that was passed to the object's constructor or, if that allocator has been replaced, a copy of the most recent replacement.

Again, the second and third constructors do not take an Allocator, so there is nothing that meets this requirement when those constructors are used.

[2018-06-02, Daniel comments and provides wording]

The introductory wording of match_results says in 31.10 [re.results] p2:

The class template match_results satisfies the requirements of an allocator-aware container and of a sequence container (26.2.1, 26.2.3) except that only operations defined for const-qualified sequence containers are supported and that the semantics of comparison functions are different from those required for a container.

This wording essentially brings us to 26.2.1 [container.requirements.general] which describes in p8 in general the usage of allocators:

[…] Copy constructors for these container types obtain an allocator by calling allocator_traits<allocator_ type>::select_on_container_copy_construction on the allocator belonging to the container being copied. Move constructors obtain an allocator by move construction from the allocator belonging to the container being moved. […]

The constructors referred to in the issue discussion are the copy constructor and move constructor of match_results, so we know already what the required effects are supposed to be.

26.2.1 [container.requirements.general] p8 also says more to this allocator topic a bit latter:

[…] All other constructors for these container types take a const allocator_type& argument. [Note: If an invocation of a constructor uses the default value of an optional allocator argument, then the Allocator type must support value-initialization. — end note] A copy of this allocator is used for any memory allocation and element construction performed, by these constructors and by all member functions, during the lifetime of each container object or until the allocator is replaced. […]

Further requirements imposed on two of the three match_results constructors can be derived from Table 80 — "Allocator-aware container requirements" via the specified expressions

X()
X(m)
X(rv)

In other words: The existing wording does already say everything that it said by 31.10.1 [re.results.const] p1 (end even more), except for possibly the tiny problem that

match_results(const Allocator& a = Allocator());

uses "const Allocator&" instead of "const allocator_type&" in the signature, albeit even that deviation shouldn't change the intended outcome, which is IMO crystal-clear when looking at sub-clauses 26.2.1 [container.requirements.general] and 26.2.3 [sequence.reqmts] as a whole.

That directly makes two mutually exclusive solutions feasible:

My suggestion is to favour for the first option, because attempting to provide extra wording that refers to allocators and the three constructors may lead to the false impression that no further allocator-related requirements hold for type match_results which are not explicitly repeated here again.

[2018-06, Rapperswil]

The group agrees with the provided resolution. Move to Ready.

Proposed resolution:

This wording is relative to N4750.

  1. Edit 31.10.1 [re.results.const] as indicated:

    -1- In all match_results constructors, a copy of the Allocator argument shall be used for any memory allocation performed by the constructor or member functions during the lifetime of the object.


2184(i). Muddled allocator requirements for match_results assignments

Section: 31.10.1 [re.results.const], 31.10.6 [re.results.all] Status: Ready Submitter: Pete Becker Opened: 2012-08-29 Last modified: 2018-06-11

Priority: 3

View other active issues in [re.results.const].

View all other issues in [re.results.const].

View all issues with Ready status.

Discussion:

The effects of the two assignment operators are specified in Table 141. Table 141 makes no mention of allocators, so, presumably, they don't touch the target object's allocator. That's okay, but it leaves the question: match_results::get_allocator() is supposed to return "A copy of the Allocator that was passed to the object's constructor or, if that allocator has been replaced, a copy of the most recent replacement"; if assignment doesn't replace the allocator, how can the allocator be replaced?

[2018-06-04, Daniel comments and provides wording]

Similar to the reasoning provided in the 2018-06-02 comment in LWG 2183, it is possible to refer to the introductory wording of match_results which says in 31.10 [re.results] p2:

The class template match_results satisfies the requirements of an allocator-aware container and of a sequence container (26.2.1, 26.2.3) except that only operations defined for const-qualified sequence containers are supported and that the semantics of comparison functions are different from those required for a container.

Again, similar to LWG 2183, this allows us to deduce the required effects of the copy/move assignment operators discussed here, because 26.2.1 [container.requirements.general] p8 also says:

[…] The allocator may be replaced only via assignment or swap(). Allocator replacement is performed by copy assignment, move assignment, or swapping of the allocator only if allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value, allocator_traits<allocator_type>::propagate_on_container_move_assignment::value, or allocator_traits<allocator_type>::propagate_on_container_swap::value is true within the implementation of the corresponding container operation. In all container types defined in this Clause, the member get_allocator() returns a copy of the allocator used to construct the container or, if that allocator has been replaced, a copy of the most recent replacement. […]

So this wording already specifies everything we need, except for the problem that 31.10 [re.results] p2 quoted above restricts to operations supported by a const-qualified sequence container, which of-course would exclude the copy assignment and the move assignment operators. But given that these mutable definitions are defined for match_results, it seems that the only fix needed is to adjust 31.10 [re.results] p2 a bit to ensure that both assignment operators are covered (again) by the general allocator-aware container wording.

[2018-06, Rapperswil]

The group generally likes the suggested direction, but would prefer the changed wording to say effectively "except that only copy assignment, move assignment, and operations defined...". Once applied, move to ready.

Previous resolution [SUPERSEDED]:

This wording is relative to N4750.

  1. Edit 31.10 [re.results] as indicated:

    -2- The class template match_results satisfies the requirements of an allocator-aware container and of a sequence container (26.2.1 [container.requirements.general], 26.2.3 [sequence.reqmts]) except that besides copy assignment and move assignment only operations defined for const-qualified sequence containers are supported and that the semantics of comparison functions are different from those required for a container.

[2018-06-06, Daniel updates wording]

Proposed resolution:

This wording is relative to N4750.

  1. Edit 31.10 [re.results] as indicated:

    -2- The class template match_results satisfies the requirements of an allocator-aware container and of a sequence container (26.2.1 [container.requirements.general], 26.2.3 [sequence.reqmts]) except that only copy assignment, move assignment, and operations defined for const-qualified sequence containers are supported and that the semantics of comparison functions are different from those required for a container.


2412(i). promise::set_value() and promise::get_future() should not race

Section: 33.6.6 [futures.promise], 33.6.10.1 [futures.task.members] Status: Ready Submitter: Jonathan Wakely Opened: 2014-06-23 Last modified: 2018-06-11

Priority: 3

View other active issues in [futures.promise].

View all other issues in [futures.promise].

View all issues with Ready status.

Discussion:

The following code has a data race according to the standard:

std::promise<void> p;
std::thread t{ []{
  p.get_future().wait();
}};
p.set_value();
t.join();

The problem is that both promise::set_value() and promise::get_future() are non-const member functions which modify the same object, and we only have wording saying that the set_value() and wait() calls (i.e. calls setting and reading the shared state) are synchronized.

The calls don't actually access the same memory locations, so the standard should allow it. My suggestion is to state that calling get_future() does not conflict with calling the various functions that make the shared state ready, but clarify with a note that this does not imply any synchronization or "happens before", only being free from data races.

[2015-02 Cologne]

Handed over to SG1.

[2016-10-21, Nico comments]

After creating a promise or packaged task one thread can call get_future() while another thread can set values/exceptions (either directly or via function call). This happens very easily.

Consider:

promise<string> p;
thread t(doSomething, ref(p));
cout << "result: " << p.get_future().get() << endl;

AFAIK, this is currently UB due to a data race (calling get_future() for the promise might happen while setting the value in the promise).

Yes, a fix is pretty easy:

promise<string> p;
future<string> f(p.get_future());
thread t(doSomething, ref(p));
cout << "result: " << f.get() << endl;

but I would like to have get_future() and setters be synchronized to avoid this UB.

This would especially make the use of packaged tasks a lot easier. Consider:

vector<packaged_task<int(char)>> tasks;
packaged_task<int(char)> t1(func);

// start separate thread to run all tasks:
auto futCallTasks = async(launch::async, callTasks, ref(tasks));

for (auto& fut : tasksResults) {
  cout << "result: " << fut.get_future().get() << endl; // OOPS: UB
}

Again, AFAIK, this program currently is UB due to a data race. Instead, currently I'd have to program, which is a lot less convenient:

vector<packaged_task<int(char)>> tasks;
vector<future<int>> tasksResults;
packaged_task<int(char)> t1(func);
tasksResults.push_back(t1.getFuture()));
tasks.push_back(move(t1));

// start separate thread to run all tasks:
auto futCallTasks = async(launch::async, callTasks, ref(tasks));

for (auto& fut : tasksResults) {
  cout << "result: " << fut.get() << endl;
}

With my naive thinking I see not reason not to guarantee that these calls synchronize (as get_future returns an "address/reference" while all setters set the values there).

Previous resolution [SUPERSEDED]:

This wording is relative to N3936.

  1. Change 33.6.6 [futures.promise] around p12 as indicated:

    future<R> get_future();
    

    -12- Returns: A future<R> object with the same shared state as *this.

    -?- Synchronization: Calls to this function do not conflict (6.8.2 [intro.multithread]) with calls to set_value, set_exception, set_value_at_thread_exit, or set_exception_at_thread_exit. [Note: Such calls need not be synchronized, but implementations must ensure they do not introduce data races. — end note]

    -13- Throws: future_error if *this has no shared state or if get_future has already been called on a promise with the same shared state as *this.

    -14- Error conditions: […]

  2. Change 33.6.10.1 [futures.task.members] around p13 as indicated:

    future<R> get_future();
    

    -13- Returns: A future<R> object that shares the same shared state as *this.

    -?- Synchronization: Calls to this function do not conflict (6.8.2 [intro.multithread]) with calls to operator() or make_ready_at_thread_exit. [Note: Such calls need not be synchronized, but implementations must ensure they do not introduce data races. — end note]

    -14- Throws: a future_error object if an error occurs.

    -15- Error conditions: […]

[2017-02-28, Kona]

SG1 has updated wording for LWG 2412. SG1 voted to move this to Ready status by unanimous consent.

[2017-03-01, Kona, SG1]

GeoffR to forward revised wording.

[2018-06, Rapperswil, Wednesday evening session]

JW: lets move on and I'll file another issue to make the wording better
BO: the current wording is better than what there before
JM: ACTION I should file an editorial issue to clean up on how to refer to [res.on.data.races]: raised editorial issue 2097
ACTION: move to Ready

Daniel rebases wording to N4750.

Proposed resolution:

This wording is relative to N4750.

  1. Change 33.6.6 [futures.promise] around p12 as indicated:

    future<R> get_future();
    

    -12- Returns: A future<R> object with the same shared state as *this.

    -?- Synchronization: Calls to this function do not introduce data races (6.8.2 [intro.multithread]) with calls to set_value, set_exception, set_value_at_thread_exit, or set_exception_at_thread_exit. [Note: Such calls need not synchronize with each other. — end note]

    -13- Throws: future_error if *this has no shared state or if get_future has already been called on a promise with the same shared state as *this.

    -14- Error conditions: […]

  2. Change 33.6.10.1 [futures.task.members] around p13 as indicated:

    future<R> get_future();
    

    -13- Returns: A future object that shares the same shared state as *this.

    -?- Synchronization: Calls to this function do not introduce data races (6.8.2 [intro.multithread]) with calls to operator() or make_ready_at_thread_exit. [Note: Such calls need not synchronize with each other. — end note]

    -14- Throws: a future_error object if an error occurs.

    -15- Error conditions: […]


2682(i). filesystem::copy() won't create a symlink to a directory

Section: 30.11.14.3 [fs.op.copy] Status: Ready Submitter: Jonathan Wakely Opened: 2016-04-19 Last modified: 2018-06-11

Priority: 2

View other active issues in [fs.op.copy].

View all other issues in [fs.op.copy].

View all issues with Ready status.

Discussion:

(First raised in c++std-lib-38544)

filesystem::copy doesn't create a symlink to a directory in this case:

copy("/", "root", copy_options::create_symlinks);

If the first path is a file then a symlink is created, but I think my implementation is correct to do nothing for a directory. We get to bullet 30.11.14.3 [fs.op.copy] (3.6) where is_directory(f) is true, but options == create_symlinks, so we go to the next bullet (3.7) which says "Otherwise, no effects."

I think the case above should either create a symlink, or should report an error. GNU cp -s gives an error in this case, printing "omitting directory '/'". An error seems reasonable, you can use create_symlink to create a symlink to a directory.

[2016-05 Issues Telecon]

This is related to 2681; and should be considered together.

[2016-08 Chicago]

Wed AM: Move to Tentatively Ready

Previous resolution [SUPERSEDED]:

Add a new bullet following (3.6) in 30.11.14.3 [fs.op.copy] as shown:

[2016-10-16, Eric reopens and provides improved wording]

The current PR makes using copy(...) to copy/create a directory symlink an error. For example, the following is now an error:

copy("/", "root", copy_options::create_symlinks);

However the current PR doesn't handle the case where both copy_options::create_symlinks and copy_options::recursive are specified. This case is still incorrectly handled by bullet (3.6) [fs.op.copy].

I suggest we move the PR before this bullet so that it catches the recursive copy case, since currently the conditions are ordered:

3.6 Otherwise if is_directory(f) && (bool(options & copy_options::recursive) || ...)
3.X Otherwise if is_directory(f) && bool(options & copy_options::create_symlinks)

So 3.6 catches create_symlinks | recursive but I believe we want 3.X to handle it instead.

[2018-01-26 issues processing telecon]

Status to 'Review'; we think this is OK but want some implementation experience before adopting it.

[2018-01-29 Jonathan says]

The proposed resolution for LWG 2682 has been in GCC's Filesystem TS implementation for more than a year. It's also in our std::filesystem implementation on the subversion trunk.

[2018-06; Rapperswil Wednesday evening]

JW: can we use the words we are shipping already since two years?
BO: what we got is better than what we had before
no objection to moving to Ready
ACTION move to Ready
ACTION link 2682 and LWG 3057 and set a priority 2 and look at 3057 in San Diego

Daniel rebases wording to N4750.

Proposed resolution:

This wording is relative to N4750.

  1. Add a new bullet before (4.8) in 30.11.14.3 [fs.op.copy] as shown:

    1. (4.7) — Otherwise, if is_regular_file(f), then:

      […]
    2. (4.?) — Otherwise, if

      is_directory(f) && 
      (options & copy_options::create_symlinks) != copy_options::none
      

      then report an error with an error_code argument equal to make_error_code(errc::is_a_directory).

    3. (4.8) — Otherwise, if

      is_directory(f) &&
      ((options & copy_options::recursive) != copy_options::none ||
      options == copy_options::none)
      

      then:

      1. (4.8.1) — If exists(t) is false, then create_directory(to, from).

      2. (4.8.2) — Then, iterate over the files in from, as if by

        for (const directory_entry& x : directory_iterator(from))
          copy(x.path(), to/x.path().filename(),
            options | copy_options::in-recursive-copy);
        

        where in-recursive-copy is a bitmask element of copy_options that is not one of the elements in 30.11.9.3 [fs.enum.copy.opts].

    4. (4.9) — Otherwise, for the signature with argument ec, ec.clear().

    5. (4.10) — Otherwise, no effects.


2697(i). [concurr.ts] Behavior of future/shared_future unwrapping constructor when given an invalid future

Section: 99 [concurr.ts::futures.unique_future], 99 [concurr.ts::futures.shared_future] Status: Ready Submitter: Tim Song Opened: 2016-04-22 Last modified: 2018-06-11

Priority: 2

View other active issues in [concurr.ts::futures.unique_future].

View all other issues in [concurr.ts::futures.unique_future].

View all issues with Ready status.

Discussion:

Addresses: concurr.ts

In the concurrency TS, the future/shared_future unwrapping constructors

future(future<future<R>>&&) noexcept;
shared_future(future<shared_future<R>>&& rhs) noexcept;

appear to implicitly require rhs be valid (e.g., by referring to its shared state, and by requiring a valid() == true postcondition). However, they are also marked noexcept, suggesting that they are wide-contract, and also makes the usual suggested handling for invalid futures, throwing a future_error, impossible.

Either the noexcept should be removed, or the behavior with an invalid future should be specified.

Original resolution alternative #1 [NOT CHOSEN]:

This wording is relative to N4577.

Strike the noexcept on these constructors in 99 [concurr.ts::futures.unique_future]/1-2 and 99 [concurr.ts::futures.shared_future]/1-2, and optionally add a Requires: rhs.valid() == true paragraph.

[2016-11-12, Issaquah]

Sat PM: We prefer alternative #2 - Move to review

[2018-06; Rapperswil, Wednesday evening session]

DR: there is a sentence ended followed by an entirely new sentence
JM: so the period should be a semicolon in both edits
MC: ACTION I can make the change editorially
ACTION move to Ready

Proposed resolution:

This wording is relative to N4577.

Alternative #2: Specify that an empty (shared_)future object is constructed if rhs is invalid, and adjust the postcondition accordingly.

  1. Edit 99 [concurr.ts::futures.unique_future] as indicated:

    future(future<future<R>>&& rhs) noexcept;
    

    -3- Effects: If rhs.valid() == false, constructs an empty future object that does not refer to a shared state. Otherwise, cConstructs a future object from the shared state referred to by rhs. T; the future becomes ready when one of the following occurs:

    • Both the rhs and rhs.get() are ready. The value or the exception from rhs.get() is stored in the future's shared state.

    • rhs is ready but rhs.get() is invalid. An exception of type std::future_error, with an error condition of std::future_errc::broken_promise is stored in the future's shared state.

    -4- Postconditions:

    • valid() == truevalid() returns the same value as rhs.valid() prior to the constructor invocation..

    • rhs.valid() == false.

  2. Edit 99 [concurr.ts::futures.shared_future] as indicated:

    shared_future(future<shared_future<R>>&& rhs) noexcept;
    

    -3- Effects: If rhs.valid() == false, constructs an empty shared_future object that does not refer to a shared state. Otherwise, cConstructs a shared_future object from the shared state referred to by rhs. T; the shared_future becomes ready when one of the following occurs:

    • Both the rhs and rhs.get() are ready. The value or the exception from rhs.get() is stored in the shared_future's shared state.

    • rhs is ready but rhs.get() is invalid. The shared_future stores an exception of type std::future_error, with an error condition of std::future_errc::broken_promise.

    -4- Postconditions:

    • valid() == truevalid() returns the same value as rhs.valid() prior to the constructor invocation..

    • rhs.valid() == false.


2936(i). Path comparison is defined in terms of the generic format

Section: 30.11.7.4.8 [fs.path.compare] Status: Ready Submitter: Billy Robert O'Neal III Opened: 2017-02-21 Last modified: 2018-06-11

Priority: 2

View all issues with Ready status.

Discussion:

Currently, path comparison is defined elementwise, which implies a conversion from the native format (implied by native() returning const string&). However, the conversion from the native format to the generic format might not be information preserving. This would allow two paths a and b to say a.compare(b) == 0, but a.native().compare(b.native()) != 0 as a result of this missing information, which is undesirable. We only want that condition to happen if there are redundant directory separators. We also don't want to change the path comparison to be in terms of the native format, due to Peter Dimov's example where we want path("a/b") to sort earlier than path("a.b"), and we want path("a/b") == path("a//////b").

Citing a Windows example, conversion to the generic format is going to have to drop alternate data streams. This might give path("a/b:ads") == path("a/b"). I think I should consider the alternate data streams as part of the path element though, so this case might be fine, so long as I make path("b:ads").native() be "b:ads". This might not work for our z/OS friends though, or for folks where the native format looks nothing like the generic format.

Additionally, this treats root-directory specially. For example, the current spec wants path("c:/a/b") == path("c:/a////b"), but path("c:/a/b") != path("c:///a/b"), because native() for the root-directory path element will literally be the slashes or preferred separators.

This addresses similar issues to those raised in US 57 — it won't make absolute paths sort at the beginning or end but it will make paths of the same kind sort together.

[2017-03-04, Kona Saturday morning]

We decided that this had seen so much churn that we would postpone looking at this until Toronto

[2017-07 Toronto Thurs Issue Prioritization]

Priority 2

[2016-07, Toronto Saturday afternoon issues processing]

Billy to reword after Davis researches history about ordering. Status to Open.

Previous resolution [SUPERSEDED]:

This wording is relative to N4640.

  1. Make the following edits to 30.11.7.4.8 [fs.path.compare]:

    int compare(const path& p) const noexcept;
    

    -1- Returns:

    — Let rootNameComparison be the result of this->root_name().native().compare(p.root_name().native()). If rootNameComparison is not 0, rootNameComparison; otherwise,

    — If this->has_root_directory() and !p.has_root_directory(), a value less than 0; otherwise,

    — If !this->has_root_directory() and p.has_root_directory(), a value greater than 0; otherwise,

    — A value greater than, less than, or equal to 0, ordering the paths in a depth-first traversal order.

    -?- [Note: For POSIX and Windows platforms, this is accomplished by lexicographically ordering the half-open ranges [begin(), end()) of this->relative_path() and p.relative_path() as follows:

    — A value less than 0, if native() for the elements of *this->relative_path() are lexicographically less than native() for the elements of p.relative_path(); otherwise,

    — a value greater than 0, if native() for the elements of *this->relative_path() are lexicographically greater than native() for the elements of p.relative_path(); otherwise,

    0.

    end note]

    -2- Remarks: The elements are determined as if by iteration over the half-open range [begin(), end()) for *this and p.

    int compare(const string_type& s) const
    int compare(basic_string_view<value_type> s) const;
    

    -3- Returns: compare(path(s))

    [Editor's note: Delete paragraph 3 entirely and merge the value_type overload with those above.]

    int compare(const value_type* s) const
    

    -4- ReturnsEffects: Equivalent to return compare(path(s));.

[2018-01-26 issues processing telecon]

Status set to 'Review'. We like the wording, but would like to see some implementation experience.

Previous resolution [SUPERSEDED]:

This wording is relative to N4659.

  1. Make the following edits to 30.11.7.4.8 [fs.path.compare]:

    int compare(const path& p) const noexcept;
    

    -1- Returns:

    — Let rootNameComparison be the result of this->root_name().native().compare(p.root_name().native()). If rootNameComparison is not 0, rootNameComparison; otherwise,

    — If this->has_root_directory() and !p.has_root_directory(), a value less than 0; otherwise,

    — If !this->has_root_directory() and p.has_root_directory(), a value greater than 0; otherwise,

    a value less than 0, iIf native() for the elements of *this->relative_path() are lexicographically less than native() for the elements of p.relative_path(), a value less than 0; otherwise,

    a value greater than 0, iIf native() for the elements of *this->relative_path() are lexicographically greater than native() for the elements of p.relative_path(), a value greater than 0; otherwise,

    0.

    -2- Remarks: The elements are determined as if by iteration over the half-open range [begin(), end()) for *this and p.

    int compare(const string_type& s) const
    int compare(basic_string_view<value_type> s) const;
    

    -3- Returns: compare(path(s))

    [Editor's note: Delete paragraph 3 entirely and merge the value_type overload with those above.]

    int compare(const value_type* s) const
    

    -4- ReturnsEffects: Equivalent to return compare(path(s));.

[2018-02-13 Billy improves wording]

The revised wording has the effect to invert the ordering of the added new bullets (2) and (3), the effect of this change is that

path("c:/").compare("c:")

compares greater, not less.

[2018-06, Rapperswil Wednesday evening]

Agreement to move that to Ready, Daniel rebased to N4750.

Proposed resolution:

This wording is relative to N4750.

  1. Make the following edits to 30.11.7.4.8 [fs.path.compare]:

    int compare(const path& p) const noexcept;
    

    -1- Returns:

    — Let rootNameComparison be the result of this->root_name().native().compare(p.root_name().native()). If rootNameComparison is not 0, rootNameComparison; otherwise,

    — If !this->has_root_directory() and p.has_root_directory(), a value less than 0; otherwise,

    — If this->has_root_directory() and !p.has_root_directory(), a value greater than 0; otherwise,

    a value less than 0, iIf native() for the elements of *this->relative_path() are lexicographically less than native() for the elements of p.relative_path(), a value less than 0; otherwise,

    a value greater than 0, iIf native() for the elements of *this->relative_path() are lexicographically greater than native() for the elements of p.relative_path(), a value greater than 0; otherwise,

    0.

    -2- Remarks: The elements are determined as if by iteration over the half-open range [begin(), end()) for *this and p.

    int compare(const string_type& s) const
    int compare(basic_string_view<value_type> s) const;
    

    -3- Returns: compare(path(s))

    [Editor's note: Delete paragraph 3 entirely and merge the value_type overload with those above.]

    int compare(const value_type* s) const
    

    -4- ReturnsEffects: Equivalent to: return compare(path(s));.


2995(i). basic_stringbuf default constructor forbids it from using SSO capacity

Section: 30.8.2.1 [stringbuf.cons] Status: Ready Submitter: Jonathan Wakely Opened: 2017-07-07 Last modified: 2018-06-11

Priority: 3

View other active issues in [stringbuf.cons].

View all other issues in [stringbuf.cons].

View all issues with Ready status.

Discussion:

[stringbuf.cons] says that the default constructor initializes the base class as basic_streambuf() which means the all the pointers to the input and output sequences (pbase, eback etc) are all required to be null.

This means that a stringbuf that is implemented in terms of a Small String Optimised std::basic_string cannot make us of the string's initial capacity, and so cannot avoid a call to the overflow virtual function even for small writes. In other words, the following assertions must pass:

#include <sstream>
#include <cassert>

bool overflowed = false;

struct SB : std::stringbuf
{
  int overflow(int c) {
    assert( pbase() == nullptr );
    overflowed = true;
    return std::stringbuf::overflow(c);
  }
};

int main()
{
  SB sb;
  sb.sputc('1');
  assert(overflowed);
}

This is an unnecessary pessimisation. Implementations should be allowed to use the SSO buffer immediately and write to it without calling overflow. Libc++ already does this, so is non-conforming.

[2017-07 Toronto Tuesday PM issue prioritization]

Priority 3; is this affected by Peter Sommerlad's paper P0407R1?

[2018-06 Rapperswil Wednesday issues processing]

Status to Ready

Proposed resolution:

This wording is relative to N4659.

  1. Edit 30.8.2.1 [stringbuf.cons] as indicated:

    explicit basic_stringbuf(
      ios_base::openmode which = ios_base::in | ios_base::out);
    

    -1- Effects: Constructs an object of class basic_stringbuf, initializing the base class with basic_streambuf() (30.6.3.1 [streambuf.cons]), and initializing mode with which. It is implementation-defined whether the sequence pointers (eback(), gptr(), egptr(), pbase(), pptr(), epptr()) are initialized to null pointers.

    -2- Postconditions: str() == "".


2996(i). Missing rvalue overloads for shared_ptr operations

Section: 23.11.3 [util.smartptr.shared], 23.11.3.9 [util.smartptr.shared.cast] Status: Ready Submitter: Geoffrey Romer Opened: 2017-07-07 Last modified: 2018-06-11

Priority: Not Prioritized

View other active issues in [util.smartptr.shared].

View all other issues in [util.smartptr.shared].

View all issues with Ready status.

Discussion:

The shared_ptr aliasing constructor and the shared_ptr casts are specified to take a shared_ptr by const reference and construct a new shared_ptr that shares ownership with it, and yet they have no corresponding rvalue reference overloads. That results in an unnecessary refcount increment/decrement when those operations are given an rvalue. Rvalue overloads can't be added as a conforming extension because they observably change semantics (but mostly only for code that does unreasonable things like pass an argument by move and then rely on the fact that it's unchanged), and [res.on.arguments]/p1.3 doesn't help because it only applies to rvalue reference parameters.

This issue is related to P0390R0.

[2017-07 Toronto Tuesday PM issue prioritization]

Status LEWG

[2018-06 Rapperswil Monday AM]

Move to Ready; choosing the PR in the issue as opposed to P0390R0 and rebase wording to most recent working draft

Proposed resolution:

This wording is relative to N4750.

  1. Edit 23.10.2 [memory.syn], header <memory> synopsis, as indicated:

    […]
    // 23.11.3.9 [util.smartptr.shared.cast], shared_ptr casts
    template<class T, class U>
    shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> static_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> dynamic_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> const_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> const_pointer_cast(shared_ptr<U>&& r) noexcept;
    template<class T, class U>
    shared_ptr<T> reinterpret_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
    shared_ptr<T> reinterpret_pointer_cast(shared_ptr<U>&& r) noexcept;
    […]
    
  2. Edit 23.11.3 [util.smartptr.shared], class template shared_ptr synopsis, as indicated:

    template<class T> class shared_ptr {
    public:
      […]
      // 23.11.3.1 [util.smartptr.shared.const], constructors
      […]
      template <class D, class A> shared_ptr(nullptr_t p, D d, A a);
      template<class Y> shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept;
      template<class Y> shared_ptr(shared_ptr<Y>&& r, element_type* p) noexcept;
      shared_ptr(const shared_ptr& r) noexcept;
      […]
    };
    […]
    
  3. Edit 23.11.3.1 [util.smartptr.shared.const] as indicated:

    [Drafting note: the use_count() postcondition can safely be deleted because it is redundant with the "shares ownership" wording in the Effects. — end drafting note]

    template<class Y> shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept;
    template<class Y> shared_ptr(shared_ptr<Y>&& r, element_type* p) noexcept;
    

    -14- Effects: Constructs a shared_ptr instance that stores p and shares ownership with the initial value of r.

    -15- Postconditions: get() == p && use_count() == r.use_count(). For the second overload, r is empty and r.get() == nullptr.

    -16- [Note: To avoid the possibility of a dangling pointer, the user of this constructor must ensure that p remains valid at least until the ownership group of r is destroyed. — end note]

    -17- [Note: This constructor allows creation of an empty shared_ptr instance with a non-null stored pointer. — end note]

  4. Edit 23.11.3.9 [util.smartptr.shared.cast] as indicated:

    template<class T, class U>
      shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
      shared_ptr<T> static_pointer_cast(shared_ptr<U>&& r) noexcept;
    

    -1- Requires: The expression static_cast<T*>((U*)nullptr) shall be well-formed.

    -2- Returns:

    shared_ptr<T>(rR, static_cast<typename shared_ptr<T>::element_type*>(r.get()))
    , where R is r for the first overload, and std::move(r) for the second.

    -3- [Note: The seemingly equivalent expression shared_ptr<T>(static_cast<T*>(r.get())) will eventually result in undefined behavior, attempting to delete the same object twice. — end note]

    template<class T, class U>
      shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
      shared_ptr<T> dynamic_pointer_cast(shared_ptr<U>&& r) noexcept;
    

    -4- Requires: The expression dynamic_cast<T*>((U*)nullptr) shall be well-formed. The expression dynamic_cast<typename shared_ptr<T>::element_type*>(r.get()) shall be well formed and shall have well-defined behavior.

    -5- Returns:

    1. (5.1) — When dynamic_cast<typename shared_ptr<T>::element_type*>(r.get()) returns a non-null value p, shared_ptr<T>(rR, p), where R is r for the first overload, and std::move(r) for the second.

    2. (5.2) — Otherwise, shared_ptr<T>().

    -6- [Note: The seemingly equivalent expression shared_ptr<T>(dynamic_cast<T*>(r.get())) will eventually result in undefined behavior, attempting to delete the same object twice. — end note]

    template<class T, class U>
      shared_ptr<T> const_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
      shared_ptr<T> const_pointer_cast(shared_ptr<U>&& r) noexcept;
    

    -7- Requires: The expression const_cast<T*>((U*)nullptr) shall be well-formed.

    -8- Returns:

    shared_ptr<T>(rR, const_cast<typename shared_ptr<T>::element_type*>(r.get()))
    , where R is r for the first overload, and std::move(r) for the second.

    -9- [Note: The seemingly equivalent expression shared_ptr<T>(const_cast<T*>(r.get())) will eventually result in undefined behavior, attempting to delete the same object twice. — end note]

    template<class T, class U>
      shared_ptr<T> reinterpret_pointer_cast(const shared_ptr<U>& r) noexcept;
    template<class T, class U>
      shared_ptr<T> reinterpret_pointer_cast(shared_ptr<U>&& r) noexcept;
    

    -10- Requires: The expression reinterpret_cast<T*>((U*)nullptr) shall be well-formed.

    -11- Returns:

    shared_ptr<T>(rR, reinterpret_cast<typename shared_ptr<T>::element_type*>(r.get()))
    , where R is r for the first overload, and std::move(r) for the second.

    -12- [Note: The seemingly equivalent expression shared_ptr<T>(reinterpret_cast<T*>(r.get())) will eventually result in undefined behavior, attempting to delete the same object twice. — end note]


3054(i). uninitialized_copy appears to not be able to meet its exception-safety guarantee

Section: 23.10.11.4 [uninitialized.copy] Status: Ready Submitter: Jon Cohen Opened: 2018-01-24 Last modified: 2018-06-11

Priority: 2

View all other issues in [uninitialized.copy].

View all issues with Ready status.

Discussion:

I believe that uninitialized_copy is unable to meet its exception-safety guarantee in the presence of throwing move constructors:

23.10.11 [specialized.algorithms]/1 has two statements of note for the specialized algorithms such as uninitialized_copy:

Suppose we have an input iterator Iter. Then std::move_iterator<Iter> appears to also be an input iterator. Notably, it still satisfies that (void)*a, *a is equivalent to *a for move iterator a since the dereference only forms an rvalue reference, it doesn't actually perform the move operation (27.2.3 [input.iterators] Table 95 — "Input iterator requirements").

Suppose also that we have a type T whose move constructor can throw, a range of T's [tbegin, tend), and a pointer to an uninitialized buffer of T's buf. Then std::uninitialized_copy(std::make_move_iterator(tbegin), std::make_move_iterator(tend), buf) can't possibly satisfy the property that it has no effects if one of the moves throws — we'll have a T left in a moved-from state with no way of recovering.

See here for an example in code.

It seems like the correct specification for uninitialized_copy should be that if InputIterator's operator* returns an rvalue reference and InputIterator::value_type's move constructor is not marked noexcept, then uninitialized_copy will leave the objects in the underlying range in a valid but unspecified state.

[2018-01-24, Casey comments and provides wording]

This issue points out a particular hole in the "..if an exception is thrown in the following algorithms there are no effects." wording for the "uninitialized" memory algorithms (23.10.11 [specialized.algorithms]/1) and suggests a PR to patch over said hole. The true problem here is that "no effects" is not and never has been implementable. For example, "first != last" may have observable effects that an implementation is required to somehow reverse if some later operation throws an exception.

Rather than finding problem case after problem case and applying individual patches, we should fix the root cause. If we alter the problematic sentence from [specialized.algorithms]/1 we can fix the issue once and for all and have implementable algorithms.

[2018-02-05, Priority set to 2 after mailing list discussion]

[2018-06 Rapperswil Thursday issues processing]

Status to Ready

Proposed resolution:

This wording is relative to N4713.

  1. Modify 23.10.11 [specialized.algorithms] as indicated:

    -1- […]

    Unless otherwise specified, if an exception is thrown in the following algorithms objects constructed by a placement new-expression (8.5.2.4 [expr.new]) are destroyed in an unspecified order before allowing the exception to propagatethere are no effects.

  2. Modify 23.10.11.5 [uninitialized.move] as indicated (The removed paragraphs are now unnecessary):

    template<class InputIterator, class ForwardIterator>
      ForwardIterator uninitialized_move(InputIterator first, InputIterator last,
                                         ForwardIterator result);
    

    […]

    -2- Remarks: If an exception is thrown, some objects in the range [first, last) are left in a valid but unspecified state.

    template<class InputIterator, class Size, class ForwardIterator>
      pair<InputIterator, ForwardIterator>
        uninitialized_move_n(InputIterator first, Size n, ForwardIterator result);
    

    […]

    -4- Remarks: If an exception is thrown, some objects in the range [first, std::next(first, n)) are left in a valid but unspecified state.


3116(i). OUTERMOST_ALLOC_TRAITS needs remove_reference_t

Section: 23.13.4 [allocator.adaptor.members] Status: Ready Submitter: Tim Song Opened: 2018-06-04 Last modified: 2018-06-11

Priority: 0

View all other issues in [allocator.adaptor.members].

View all issues with Ready status.

Discussion:

OUTERMOST_ALLOC_TRAITS(x) is currently defined in 23.13.4 [allocator.adaptor.members]p1 as allocator_traits<decltype(OUTERMOST(x))>. However, OUTERMOST(x), as defined and used in this subclause, is an lvalue for which decltype produces an lvalue reference. That referenceness needs to be removed before the type can be used with allocator_traits.

While we are here, the current wording for OUTERMOST uses the imprecise "if x does not have an outer_allocator() member function". What we meant to check is the validity of the expression x.outer_allocator(), not whether x has some (possibly ambiguous and/or inaccessible) member function named outer_allocator.

[2018-06 Rapperswil Thursday issues processing]

Status to Ready

Proposed resolution:

This wording is relative to N4750.

[Drafting note: The subclause only uses OUTERMOST_ALLOC_TRAITS(*this) and only in non-const member functions, so the result is also non-const. Thus, remove_reference_t is sufficient; there's no need to further remove cv-qualification. — end drafting note]

  1. Modify 23.13.4 [allocator.adaptor.members]p1 as indicated:

    -1- In the construct member functions, OUTERMOST(x) is x if x does not have an outer_allocator() member function and OUTERMOST(x.outer_allocator()) if the expression x.outer_allocator() is valid (17.9.2 [temp.deduct]) and x otherwise; OUTERMOST_ALLOC_TRAITS(x) is allocator_traits<remove_reference_t<decltype(OUTERMOST(x))>>. [Note: […] — end note]


3122(i). __cpp_lib_chrono_udls was accidentally dropped

Section: 21.3.1 [support.limits.general] Status: Tentatively Ready Submitter: Stephan T. Lavavej Opened: 2018-06-14 Last modified: 2018-06-24

Priority: 0

Discussion:

Between P0941R0 and P0941R1/P0941R2, the feature-test macro __cpp_lib_chrono_udls was dropped. It wasn't mentioned in the changelog, and Jonathan Wakely and I believe that this was unintentional.

[2018-06-23 Moved to Tentatively Ready after 5 positive votes on c++std-lib.]

Proposed resolution:

This wording is relative to the post-Rapperswil 2018 working draft.

In 21.3.1 [support.limits.general], "Table ??? - Standard library feature-test macros", add the following row:

Table ??? — Standard library feature-test macros
Macro name Value Headers
[…]
__cpp_lib_chrono_udls 201304L <chrono>
[…]