Date: Thu, 25 Sep 2025 10:09:19 -0400
On Thu, Sep 25, 2025 at 1:52 AM Jan Schultke <janschultke_at_[hidden]>
wrote:
> [Arthur wrote:]
> > struct Optional {
> > T t_;
> > const T* operator->() const { return &t_; }
> > T* operator->() { return &t_; }
> > };
> > struct SharedPtr {
> > T *p_;
> > T *operator->() const { return p_; }
> > };
> > Optional getOptional();
> > SharedPtr getSharedPtr();
> > int main() {
> > getOptional()->fn(); // getOptional() is a prvalue, and we'd like
> fn() not to be callable here
> > getSharedPtr()->fn(); // getSharedPtr() is a prvalue, but we must
> continue to call fn() here; there's nothing wrong with this code
> > }
> >
> > Now, we could at least give a (QoI) compiler warning if `expr->f()` on
> an rvalue `expr` invokes a non-const `operator->` member function. (Test
> cases here.) That seems tricky to implement, but maybe it's not so hard as
> I'm thinking.
> > In fact, my P1144 Clang fork already gives a warning if `expr = rhs` on
> an rvalue `expr` invokes a non-const `operator=`, because that's almost
> always a bug.
>
> There's no way that in your code, the compiler could tell if you're
> working with a type that has reference semantics (like SharedPtr), so
> I don't think any of this can be solved as QoI. I don't see how it
> would be warning-worthy to call a non-const member function through
> operator-> on an rvalue like getSharedPtr(). The value category of the
> SharedPtr is irrelevant.
>
Right, but the compiler can tell whether you're dealing with a
pointer-semantic SharedPtr or a value-semantic Optional. That's the easy
part. Value-semantic Optional preserves constness:
> const T* operator->() const { return &t_; }
> T* operator->() { return &t_; }
while pointer-semantic SharedPtr does not:
> T *operator->() const { return p_; }
That "easy part" is the part I was pointing to already having solved in
Clang, when (in my previous message) I pointed to the warning
<source>:13:14: warning: possibly unintended assignment to an rvalue of
type 'Obj' [-Wassign-to-class-rvalue]
13 | getObj() = 42;
| ~~~~~~~~ ^
The "hard part" is keeping track of that information all the way until we
see what you're doing with the ultimate `T` object and whether *that*
operation is "OK" to do on rvalue-ish things. Operations like assignment
and `fn() &` are "not OK" to do on rvalue-ish things.
Another thing that's "not OK" to do on an rvalue-ish thing, btw, is to
discard its value without using it.
> getOptionalRef()->set(2);
> > getSharedPtr()->set(2);
> > getPointer()->set(2);
>
> Code like this is totally fine.
>
Yes, but getOptional()->set(2) wouldn't be.
Cross-thread: I'm in favor of P3039 "Automatically generate operator->"
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p3039r0.html> too.
But it needs wording, and an implementation *of* that wording, before it
can go anywhere.
–Arthur
wrote:
> [Arthur wrote:]
> > struct Optional {
> > T t_;
> > const T* operator->() const { return &t_; }
> > T* operator->() { return &t_; }
> > };
> > struct SharedPtr {
> > T *p_;
> > T *operator->() const { return p_; }
> > };
> > Optional getOptional();
> > SharedPtr getSharedPtr();
> > int main() {
> > getOptional()->fn(); // getOptional() is a prvalue, and we'd like
> fn() not to be callable here
> > getSharedPtr()->fn(); // getSharedPtr() is a prvalue, but we must
> continue to call fn() here; there's nothing wrong with this code
> > }
> >
> > Now, we could at least give a (QoI) compiler warning if `expr->f()` on
> an rvalue `expr` invokes a non-const `operator->` member function. (Test
> cases here.) That seems tricky to implement, but maybe it's not so hard as
> I'm thinking.
> > In fact, my P1144 Clang fork already gives a warning if `expr = rhs` on
> an rvalue `expr` invokes a non-const `operator=`, because that's almost
> always a bug.
>
> There's no way that in your code, the compiler could tell if you're
> working with a type that has reference semantics (like SharedPtr), so
> I don't think any of this can be solved as QoI. I don't see how it
> would be warning-worthy to call a non-const member function through
> operator-> on an rvalue like getSharedPtr(). The value category of the
> SharedPtr is irrelevant.
>
Right, but the compiler can tell whether you're dealing with a
pointer-semantic SharedPtr or a value-semantic Optional. That's the easy
part. Value-semantic Optional preserves constness:
> const T* operator->() const { return &t_; }
> T* operator->() { return &t_; }
while pointer-semantic SharedPtr does not:
> T *operator->() const { return p_; }
That "easy part" is the part I was pointing to already having solved in
Clang, when (in my previous message) I pointed to the warning
<source>:13:14: warning: possibly unintended assignment to an rvalue of
type 'Obj' [-Wassign-to-class-rvalue]
13 | getObj() = 42;
| ~~~~~~~~ ^
The "hard part" is keeping track of that information all the way until we
see what you're doing with the ultimate `T` object and whether *that*
operation is "OK" to do on rvalue-ish things. Operations like assignment
and `fn() &` are "not OK" to do on rvalue-ish things.
Another thing that's "not OK" to do on an rvalue-ish thing, btw, is to
discard its value without using it.
> getOptionalRef()->set(2);
> > getSharedPtr()->set(2);
> > getPointer()->set(2);
>
> Code like this is totally fine.
>
Yes, but getOptional()->set(2) wouldn't be.
Cross-thread: I'm in favor of P3039 "Automatically generate operator->"
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p3039r0.html> too.
But it needs wording, and an implementation *of* that wording, before it
can go anywhere.
–Arthur
Received on 2025-09-25 14:09:34