Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix a bunch of faulty parallelism with "either". #2772

Merged
merged 3 commits into from Mar 15, 2019

Conversation

Quuxplusone
Copy link
Contributor

This is intended as a purely editorial changeset.

@@ -2883,7 +2883,7 @@
constitute the arguments to the function. The postfix expression shall
have function type or function pointer type.
For a call to a non-member function or to a static member function,
the postfix expression shall be either an lvalue that refers to a
either the postfix expression shall be an lvalue that refers to a
function (in which case the function-to-pointer standard
conversion\iref{conv.func} is suppressed on the postfix expression),
or it shall have function pointer type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is wrong. The "it" in the latter case is the postfix-expression, not the call. Suggest changing "shall be either" to "shall either be".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old code:

For a call to a non-member function or to a static member function, the postfix expression shall be either an lvalue that refers to a function (in which case the function-to-pointer standard conversion [conv.func] is suppressed on the postfix expression), or it shall have function pointer type.

My version:

For a call to a non-member function or to a static member function, either the postfix expression shall be an lvalue that refers to a function (in which case the function-to-pointer standard conversion [conv.func] is suppressed on the postfix expression), or it shall have function pointer type.

Your suggested version (IIUC):

For a call to a non-member function or to a static member function, the postfix expression shall either be an lvalue that refers to a function (in which case the function-to-pointer standard conversion [conv.func] is suppressed on the postfix expression), or have function pointer type.

I think my version and your version have the same normative meaning, so I'm okay switching to your version, but I'd like to understand better why you interpret them as meaning different things.

Perhaps related: I encountered several places — this being one of them — where the normative text says "X either shall be Y or shall be Z." I read this as literally saying that "it is required either that X be Y or that X be Z — but we're not going to tell you which of these two possible requirements is the actual requirement!" Really, every single one of these instances ought to be recast as "X shall be either Y or Z." But sometimes that leads to harder-to-parse sentences, so generally I didn't do it in this patch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your version, the "it" has a somewhat-ambiguous antecedent. A reader could parse it as either:

For a call to a non-member function or to a static member function, either the postfix expression shall be an lvalue that refers to a function (in which case the function-to-pointer standard conversion [conv.func] is suppressed on the postfix expression), or [the postfix expression] shall have function pointer type.

or as

For a call to a non-member function or to a static member function, either the postfix expression shall be an lvalue that refers to a function (in which case the function-to-pointer standard conversion [conv.func] is suppressed on the postfix expression), or [the call to a non-member function or to a static member function] shall have function pointer type.

depending on whether they think the scope of the "postfix expression" ends at the end of the clause of the "either" in which it was introduced. (Imagine that you skip from the "either" to the "or" when reading because you know you don't care about that clause.) My alternative avoids that problem.

@@ -11849,7 +11849,7 @@
\remarks
Two \tcode{shared_ptr} objects are equivalent if
they store the same pointer value and
either share ownership, or both are empty.
share ownership, or if both are empty.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. This reads like a normative change, but appears correct: "empty" is defined as "does not own a pointer", so the previous "store the same pointer value and both are empty" case was meaningless by definition.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a normative change. [util.smartptr.shared.const]/17:

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

EDIT: Apologies for the comment spam. [util.smartptr.shared.const]/14 "Constructs a shared_ptr instance that stores p and shares ownership with the initial value of r." tricked me into thinking I was wrong. The intent is that *this shares ownership with the initial value of r if and only if r owns a pointer. Para 14 utterly fails to make that clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CaseyCarter: I think you're right. I was on autopilot here and thinking about the conditions for two shared_ptrs to be equal (operator==-wise); but this section is talking about the proposed semantics for equivalence (compare_exchange_strong-wise).

No vendor has yet implemented atomic<shared_ptr<T>> as far as I can tell, but if anyone ever did, I think they'd naturally make shared_ptr<T>(p1, nullptr) "equal but non-equivalent to" shared_ptr<T>(p2, nullptr). That is, compare_exchange_strong will look at the bitwise values of the control-block pointer and the value pointer and treat the shared_ptrs as equivalent only if both pointers are equal.

https://wandbox.org/permlink/xAepLm6DdFXG5oa9

I think I'm unqualified to propose a "correct" wording here, especially if @zygoloid is correct that the original wording is meaningless. I'll take suggestions.

Copy link
Member

@jwakely jwakely Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A shared_ptr can store a pointer without owning it, and can store one pointer and own a different pointer. The original wording is not meaningless, it's correct.

There are two salient attributes which participate in the equivalence: the stored pointer, and the owned pointer. The get() member returns the stored pointer, and the address of the control block can be used as a proxy for the owned pointer. An empty shared_ptr has no control block, so the pointer is null, and two shared_ptr objects that share ownership point to the same control block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A shared_ptr can store a pointer without owning it, and can store one pointer and own a different pointer.

For completeness: a shared_ptr can also own an object of type nullptr_t via the (nullptr_t, deleter) and (nullptr_t, deleter, allocator) constructors; deleter(nullptr) is invoked when there are no more owners. (There is more than a little weirdness in shared_ptr.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so it sounds like we're settling on the original wording being correct (but maybe slightly unnaturally phrased). Thank you all for catching this! How about:

Two shared_ptr objects are equivalent if they store the same pointer value, and either they share ownership or both are empty.

(With or without the comma before "and", but definitely with no comma before "or".)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "Two shared_ptr objects are equivalent if they store the same pointer value and either share ownership or are both empty." is a bit simpler, but I certainly wouldn't object to your proposed phrasing.

EDIT: Especially given that this has already been merged.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #2793 for that change.

@zygoloid zygoloid added the changes requested Changes to the wording or approach have been requested and not yet applied. label Mar 15, 2019
@@ -12088,7 +12088,7 @@
\remarks
Two \tcode{weak_ptr} objects are equivalent if
they store the same pointer value and
either share ownership, or both are empty.
share ownership, or if both are empty.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change, however, I believe is valid: there's no way to construct a weak_ptr that both stores a pointer value and is empty.

Copy link
Member

@jwakely jwakely Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this do so?

static int i = 0;
std::shared_ptr<int> empty;
std::shared_ptr<int> empty_not_null(empty, &i);
std::weak_ptr<int> weak(empty_not_null);

It's useless though, because you can't get the stored pointer directly from a weak_ptr and constructing a shared_ptr from an empty weak pointer throws, and calling weak.lock() will return shared_ptr<int>().

Which means compare-exchange gives you a way to distinguish a "empty and stores nothing" weak_ptr from "empty but stores a pointer", which is otherwise unobservable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this do so?

I initially thought so as well, but [util.smartptr.weak.const]/4 says:

Effects: If r is empty, constructs an empty weak_ptr object; otherwise, constructs a weak_ptr object
that shares ownership with r and stores a copy of the pointer stored in r.

and now I'm wondering if I implement that correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and now I'm wondering if I implement that correctly.

Nope: we get this wrong.

EDIT: Arguably, the value of the stored pointer is unspecified in this case and for weak_ptr's default constructor. Now that the stored pointer value of an empty weak_ptr is observable, we should pin it down. I'll file an LWG issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the ctor efects, but apparently I can't read.

Nope: we get this wrong.

I think we do too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if you can't create such a weak ptr, is using subtly different wording here an improvement?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if you can't create such a weak ptr, is using subtly different wording here an improvement?

Probably not, and the existing wording will be correct if/when we apply the resolution of the LWG issue I just submitted. It may be better to leave this wording alone.

This is intended as a purely editorial changeset.
The old wording is still confusing, but my suggested change would have
been incorrect. Also, there seems to be mild disagreement over whether
an empty shared_ptr can store a pointer value or not, so *any* change
in this area shouldn't be editorial.
@jensmaurer jensmaurer removed the changes requested Changes to the wording or approach have been requested and not yet applied. label Mar 15, 2019
@jensmaurer
Copy link
Member

This was updated.

@zygoloid zygoloid merged commit 9e00558 into cplusplus:master Mar 15, 2019
@Quuxplusone Quuxplusone deleted the either branch March 27, 2019 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants