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
Conversation
source/expressions.tex
Outdated
@@ -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. |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
source/utilities.tex
Outdated
@@ -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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.)
There was a problem hiding this comment.
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".)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
source/utilities.tex
Outdated
@@ -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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 emptyweak_ptr
object; otherwise, constructs aweak_ptr
object
that shares ownership withr
and stores a copy of the pointer stored inr
.
and now I'm wondering if I implement that correctly.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
This was updated. |
This is intended as a purely editorial changeset.