Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Preserving ref qualifiers in operator-> overload

115 views
Skip to first unread message

Sam

unread,
May 23, 2020, 6:39:47 PM5/23/20
to
The following doesn't quite do what I want to do:

=============================================================================
#include <iostream>

struct x {

void func() &
{
std::cout << "lvalue" << std::endl;
}

void func() &&
{
std::cout << "rvalue" << std::endl;
}
};

struct y {

x *ptr;

y(x *ptr) : ptr{ptr} {}

x *operator->() &
{
std::cout << "Deref lvalue" << std::endl;

return ptr;
}

x *&&operator->() &&
{
std::cout << "Deref rvalue" << std::endl;

return std::move(ptr);
}
};

int main()
{
x X;

y Y{&X};

Y->func();

y{&X}->func();

return 0;
}
=============================================================================

The resulting output is:

Deref lvalue
lvalue
Deref rvalue
lvalue

What I would like to do is, somehow, an rvalue-qualified operator->()
resulting in the rvalue-qualified func() overload getting resolved (i.e.,
having the "rvalue" overload of func() called).

As pictured, x *&& doesn't work. Returning either "x &&*" or "x &&" is ill-
formed; is this even possible?

Öö Tiib

unread,
May 23, 2020, 8:19:17 PM5/23/20
to
I do not understand what you try to do but "std::move(Y)->func();" will
call rvalue-qualified version of func() of lvalue Y.



Öö Tiib

unread,
May 23, 2020, 8:28:21 PM5/23/20
to
On Sunday, 24 May 2020 03:19:17 UTC+3, Öö Tiib wrote:
>
> I do not understand what you try to do but "std::move(Y)->func();" will
> call rvalue-qualified version of func() of lvalue Y.

Nah, just leave it that I do not understand what you try to do. IOW
these operators, funcs, and rvalues ... each seem totally pointless
struggle to do something purpose of what I do not get.

Sam

unread,
May 24, 2020, 12:07:56 AM5/24/20
to
Öö Tiib writes:

> On Sunday, 24 May 2020 03:19:17 UTC+3, Öö Tiib wrote:
> >
> > I do not understand what you try to do but "std::move(Y)->func();" will
> > call rvalue-qualified version of func() of lvalue Y.

No, it does not do that, I'm afraid.

> Nah, just leave it that I do not understand what you try to do. IOW
> these operators, funcs, and rvalues ... each seem totally pointless
> struggle to do something purpose of what I do not get.

That's ok, maybe someone else will. But thanks for trying.

Öö Tiib

unread,
May 24, 2020, 6:42:18 AM5/24/20
to
That is ... unlikely.
Your pointer-like object itself being temporary and pointed at object
being temporary are unrelated and your attempt to conflate these will
likely confuse anyone, yourself included few weeks later.

Other confusing things ...

* The operator-> is not meant for returning references. It must return
pointers or (possibly references to) things that are capable to
become a pointer through implicit chain of operator-> calls. On any
case at end of the chain has to be a raw pointer.

* Pointers (irrelevant if raw or smart) to whatever references
(rvalue or lvalue) do not make sense in C++ language. Reference is
alias of actual object, not separate object.

* There is unary operator* that is meant for returning references and
so is always overloaded at same time with -> but your code does not.
.
We can return rvalue references to pointed at object like ...:

x && getX() { return std::move(*ptr); }

... but there we should also deeply consider the consequences if it
is safe and sensible thing to do. If you want to make copyable and
reseatable but not nullable pointers then std::reference_wrapper
is already what can be done in C++.


Sam

unread,
May 24, 2020, 8:45:24 AM5/24/20
to
Öö Tiib writes:

> That is ... unlikely.
> Your pointer-like object itself being temporary and pointed at object
> being temporary are unrelated and your attempt to conflate these will
> likely confuse anyone, yourself included few weeks later.

Actually, the confusion already exists. A frequently suggested idiom for
avoiding dangling references, when trying to avoid unnecessary copies:

class apple {

// .. something
};

class basket {

private:
apple fuji;

public:

const apple &getApple() const & { return fuji; }

apple getApple() && { return fuji; }
};

This generally fixes prvalue-caused dangling references. However this fails
with std::shared_ptr:

std::shared_ptr<basket> get_current_basket();

const auto &lunch=get_current_basket()->getApple();

You can do this with operator*, but not operator->. I can get the results I
want with the * operator:

x &&operator*() &&

This seems to work as intended:

x X;

y Y{&X};

(*Y).func();

(*y{&X}).func();

This ends up calling the appropriate ref-qualified overload of x, depending
upon whether an instance of y, that implements the overloaded * operator, is
an lvalue or an rvalue.

However an operator-> cannot return x &&*, unfortunately, because that would
be ill-formed.

Öö Tiib

unread,
May 25, 2020, 4:51:03 AM5/25/20
to
On Sunday, 24 May 2020 15:45:24 UTC+3, Sam wrote:
> Öö Tiib writes:
>
> > That is ... unlikely.
> > Your pointer-like object itself being temporary and pointed at object
> > being temporary are unrelated and your attempt to conflate these will
> > likely confuse anyone, yourself included few weeks later.
>
> Actually, the confusion already exists. A frequently suggested idiom for
> avoiding dangling references, when trying to avoid unnecessary copies:
>
> class apple {
>
> // .. something
> };
>
> class basket {
>
> private:
> apple fuji;
>
> public:
>
> const apple &getApple() const & { return fuji; }
>
> apple getApple() && { return fuji; }
> };

Indeed, I still do not get your point.

The components of composite should not somehow be "maybe
movable away" because the composite is temporary. Move
has to be transfer of ownership regardless if owner is temporary
or not. Neither of "getApple" above is for to move so I do not
understand how it is relevant.

> This generally fixes prvalue-caused dangling references. However this fails
> with std::shared_ptr:

Shared ownership is tricky topic. In my own code there are two usages of
shared_ptr.
1) Totally and deeply immutable objects are shared to conserve
memory, no data race as there are no write accesses ever.
2) Only one owner, rest are weak references. Such object is typically
itself synchronization measure or its access is protected by such.

So I do still not understand what you are doing there. Throw the operators
aside for a sec and try to explain what are the conceptual purposes and
relations between your objects.

Sam

unread,
May 25, 2020, 8:37:40 AM5/25/20
to
Öö Tiib writes:

> On Sunday, 24 May 2020 15:45:24 UTC+3, Sam wrote:
> > Öö Tiib writes:
> >
> > > That is ... unlikely.
> > > Your pointer-like object itself being temporary and pointed at object
> > > being temporary are unrelated and your attempt to conflate these will
> > > likely confuse anyone, yourself included few weeks later.
> >
> > Actually, the confusion already exists. A frequently suggested idiom for
> > avoiding dangling references, when trying to avoid unnecessary copies:
> >
> > class apple {
> >
> > // .. something
> > };
> >
> > class basket {
> >
> > private:
> > apple fuji;
> >
> > public:
> >
> > const apple &getApple() const & { return fuji; }
> >
> > apple getApple() && { return fuji; }
> > };
>
> Indeed, I still do not get your point.
>
> The components of composite should not somehow be "maybe
> movable away" because the composite is temporary. Move

But it certainly can. Because, after all, movable can mean "movable into
Never-Never land". The great bit bucket in the sky is one of the possible
destinations of the move. Now, try to wrap your brain around the situation
where the temporary, that implements the member access overloads, is
std::unique_ptr.

> > This generally fixes prvalue-caused dangling references. However this fails
> > with std::shared_ptr:
>
> Shared ownership is tricky topic. In my own code there are two usages of
> shared_ptr.
> 1) Totally and deeply immutable objects are shared to conserve
> memory, no data race as there are no write accesses ever.
> 2) Only one owner, rest are weak references. Such object is typically
> itself synchronization measure or its access is protected by such.
>
> So I do still not understand what you are doing there. Throw the operators
> aside for a sec and try to explain what are the conceptual purposes and
> relations between your objects.

I already did. But focusing the conceptual purposes and relations is getting
off topic.

I'm noting that operator. can be made to preserve move semantics, as
demonstrated, but operator-> apparently can't.

Öö Tiib

unread,
May 25, 2020, 9:27:59 AM5/25/20
to
On Monday, 25 May 2020 15:37:40 UTC+3, Sam wrote:

I just copy-paste here that you never addressed:
Other confusing things ...

* The operator-> is not meant for returning references. It must return
pointers or (possibly references to) things that are capable to
become a pointer through implicit chain of operator-> calls. On any
case at end of the chain has to be a raw pointer.

* Pointers (irrelevant if raw or smart) to whatever references
(rvalue or lvalue) do not make sense in C++ language. Reference is
alias of actual object, not separate object.

* There is unary operator* that is meant for returning references and
so is always overloaded at same time with -> but your code does not.
.
We can return rvalue references to pointed at object like ...:

x && getX() { return std::move(*ptr); }

... but there we should also deeply consider the consequences if it
is safe and sensible thing to do. If you want to make copyable and
reseatable but not nullable pointers then std::reference_wrapper
is already what can be done in C++.

Pointers are never to references, pointers are always to objects. That
std::unique_ptr is meant for managing dynamically allocated objects
(and not temporaries that do not need to be programmatically managed).
If the object of std::unique_ptr is itself being temporary or not does
not make any difference since the object that it manages is not
temporary.

>
> > > This generally fixes prvalue-caused dangling references. However this fails
> > > with std::shared_ptr:
> >
> > Shared ownership is tricky topic. In my own code there are two usages of
> > shared_ptr.
> > 1) Totally and deeply immutable objects are shared to conserve
> > memory, no data race as there are no write accesses ever.
> > 2) Only one owner, rest are weak references. Such object is typically
> > itself synchronization measure or its access is protected by such.
> >
> > So I do still not understand what you are doing there. Throw the operators
> > aside for a sec and try to explain what are the conceptual purposes and
> > relations between your objects.
>
> I already did. But focusing the conceptual purposes and relations is getting
> off topic.
>
> I'm noting that operator. can be made to preserve move semantics, as
> demonstrated, but operator-> apparently can't.

Operator. is not overloadable; operator* is dereference operator and so
should return reference; operator-> is member access operator and so
should return pointer.

Sam

unread,
May 25, 2020, 10:48:40 AM5/25/20
to
Öö Tiib writes:

> On Monday, 25 May 2020 15:37:40 UTC+3, Sam wrote:
>
> I just copy-paste here that you never addressed:

Feel free to copy-paste your random musings when you go off-tangent, and
start wandering the countryside looking for someone to argue with. But
that's all that you will accomplish with that: a perfectly-executed
copy/paste.

The original question, that was axed, was whether there's a Jedi mind-trick
that allows propagation of ref-overloading with the -> operator; i.e. if the
operator was a &&-qualified overload, so would be the overload in the object
returned by the operator.

This is trivially doable with operator*, but not with operator->, apparently.

That's all I wanted to know; and I'm now pretty sure that there isn't. It's
just a glitch in the matrix that this is doable with operator*() but not
with operator->(). That's unfortunate, but that's the way it is.

Whether you don't think much of the merits of doing this is a slightly
different topic, something that …I'm going to think about someday. But not
today.

> Other confusing things ...
>
> * The operator-> is not meant for returning references. It must return

I never claimed that they were.

> * Pointers (irrelevant if raw or smart) to whatever references
> (rvalue or lvalue) do not make sense in C++ language. Reference is

I never claimed that they do. Another thing that doesn't make any sense in
the C++ language is for operator++ to receive an int argument. This also
"irrelevant" to a simple mind. But it does serve a particular, important
purpose.

The same principle applies here as well, but if you're interested in
continuing this argument, you're welcome to find a nearby mirror to help you.

Juha Nieminen

unread,
May 26, 2020, 2:42:58 AM5/26/20
to
Sam <s...@email-scan.com> wrote:
> Öö Tiib writes:
>> On Monday, 25 May 2020 15:37:40 UTC+3, Sam wrote:
>> I just copy-paste here that you never addressed:
>
> Feel free to copy-paste your random musings when you go off-tangent, and
> start wandering the countryside looking for someone to argue with. But
> that's all that you will accomplish with that: a perfectly-executed
> copy/paste.

I don't understand your attitude. His remarks are completely valid.
operator->() has very specific semantic meaning in C++, and trying to make
it do something else is only going to cause problems. The only reason why
you would try to make it do something else is if you are writing some kind
of obfuscated C++ or something.

> The original question, that was axed

Indeed, it was axed. Conversation over.

Sam

unread,
May 26, 2020, 7:06:15 AM5/26/20
to
Juha Nieminen writes:

> Sam <s...@email-scan.com> wrote:
> > Öö Tiib writes:
> >> On Monday, 25 May 2020 15:37:40 UTC+3, Sam wrote:
> >> I just copy-paste here that you never addressed:
> >
> > Feel free to copy-paste your random musings when you go off-tangent, and
> > start wandering the countryside looking for someone to argue with. But
> > that's all that you will accomplish with that: a perfectly-executed
> > copy/paste.
>
> I don't understand your attitude. His remarks are completely valid.

So are mine.

> operator->() has very specific semantic meaning in C++, and trying to make
> it do something else is only going to cause problems.

Pretty much everything that can be done in C++ can cause problems, in some
form or fashion.

> The only reason why
> you would try to make it do something else is if you are writing some kind
> of obfuscated C++ or something.

I don't think it's obfuscated at all that if you have a temporary,
dereferencing it also gets you a temporary. It's a very simple concept. Not
sure why some people can't wrap their brains around it.

And I even mentioned a laughably trivial example where this is useful, a
std::unique_ptr prvalue.

And I even pointed out something else: a simple experiment shows that you
can easily do this /already/ with operator*, even though, like its cousin,
it also has "very specific semantic meaning in C++". So, I hate to be the
bearer of bad news, but just because something has "very specific semantic
meaning in C++", in the grand scheme of nothing it means …well, you got me,
it means absolutely nothing.

By the way, did you hear that operator++ can take an optional int parameter?

> > The original question, that was axed
>
> Indeed, it was axed. Conversation over.

I wasn't conversing with you in the first place.

Manfred

unread,
May 26, 2020, 7:52:24 AM5/26/20
to
Probably because what you are trying to "dereference" here is an object
of pointer type (specifically the result of operator ->, which is
required to be of pointer type), and as such it is an object on its own.
In particular, if you have an object of pointer type that is a
temporary, the object pointed to is in general /not/ a temporary - in
fact it is most commonly not, unless you exercise some peculiar gymnastics.

>
> And I even mentioned a laughably trivial example where this is useful, a
> std::unique_ptr prvalue.

In this example, the object pointed to (owned) by the std::unique_ptr
prvalue is definitely not a temporary: it is allocated on the heap (aka
"free store") and its lifetime is managed by the std::unique_ptr object.
On the contrary, the lifetime if temporaries is managed by the language.

>
> And I even pointed out something else: a simple experiment shows that
> you can easily do this /already/ with operator*, even though, like its
> cousin, it also has "very specific semantic meaning in C++". So, I hate
> to be the bearer of bad news, but just because something has "very
> specific semantic meaning in C++", in the grand scheme of nothing it
> means …well, you got me, it means absolutely nothing.

The key point, as Öö Tiib wrote, is that pointers and references are
very different things (even if references may be implemented as hidden
pointers in some cases) - the former is an object on its own, the latter
is not an object.

Rvalue qualifications apply to references only, not to pointers.

Sam

unread,
May 26, 2020, 8:42:19 AM5/26/20
to
Manfred writes:

> On 5/26/2020 1:06 PM, Sam wrote:
>
>>
>> And I even mentioned a laughably trivial example where this is useful, a
>> std::unique_ptr prvalue.
>
> In this example, the object pointed to (owned) by the std::unique_ptr
> prvalue is definitely not a temporary: it is allocated on the heap (aka
> "free store") and its lifetime is managed by the std::unique_ptr object. On
> the contrary, the lifetime if temporaries is managed by the language.

Except that when the real, authentic, 100% original formula, std::unique_ptr
temporary goes away, so does its referenced object. It will cease to exist.
It will be no more. It will join the choir invisible. It will become an ex-
object.

Because that's how it works, by definition. This is not a complicated
concept.

Ref-qualifiers on class methods exist precisely to crack that nut, to
provide different overloads when they're being called on an object that will
soon cease to exist, will be no more, join the choir invisible, and become
an ex-object; and thusly be able to do something sensible about it.

Except that they won't work here.

Again, they'll work just fine when invoked as (*ptr).method(), (if
appropriately overloaded) but not as ptr->method(). No way to make that work.

> The key point, as Öö Tiib wrote, is that pointers and references are very
> different things (even if references may be implemented as hidden pointers
> in some cases) - the former is an object on its own, the latter is not an
> object.
>
> Rvalue qualifications apply to references only, not to pointers.

Except that everywhere one looks, a->b is always defined as "just like
(*a).b", but you can't overload it in this manner. Too bad.

After thinking about it, I can kind of see a way to make it happen, to have
an overloaded "operator->" hacked up so that a
reasonable facsimile of a unique_ptr's, ->func() end up selecting a different overload in
the referenced object. But it's too fugly:

class some_big_object { /* ... */ };

struct hidden_base {

some_big_object func()
{
return some_big_object();
}

virtual some_big_object rfunc()=0;
};

class real_object : public hidden_base {

some_big_object something;

public:

some_big_object &func() { return something; }

some_big_object rfunc() override { return something; }
};

Now, a custom smart pointer can have a normal "operator->()" overload that
returns a real_object* and a "operator->() &&" overload that returns a
pointer to the hidden_base. Quite fugly, but it'll work.

Manfred

unread,
May 26, 2020, 11:07:13 AM5/26/20
to
On 5/26/2020 2:42 PM, Sam wrote:
> Manfred writes:
>
>> On 5/26/2020 1:06 PM, Sam wrote:
>>
>>>
>>> And I even mentioned a laughably trivial example where this is
>>> useful, a std::unique_ptr prvalue.
>>
>> In this example, the object pointed to (owned) by the std::unique_ptr
>> prvalue is definitely not a temporary: it is allocated on the heap
>> (aka "free store") and its lifetime is managed by the std::unique_ptr
>> object. On the contrary, the lifetime if temporaries is managed by the
>> language.
>
> Except that when the real, authentic, 100% original formula,
> std::unique_ptr temporary goes away, so does its referenced object. It
> will cease to exist. It will be no more. It will join the choir
> invisible. It will become an ex-object.

Yes, it will be delete'd. Still, technically it is not a temporary.

>
> Because that's how it works, by definition. This is not a complicated
> concept.
>
> Ref-qualifiers on class methods exist precisely to crack that nut, to
> provide different overloads when they're being called on an object that
> will soon cease to exist, will be no more, join the choir invisible, and
> become an ex-object; and thusly be able to do something sensible about it.
>
> Except that they won't work here.

The main rationale behind rvalue references is to provide support for
move semantics, and ref-qualifiers on members have this goal too.
I think that what you are trying to achieve here is some hybrid design
wherein a subobject may be copied (or possibly moved?) out of a
temporary object.
I see that you are complaining about a theoretical asymmetry in the
language design where it provides rvalue references and no rvalue
pointers, however I don't see that as a flaw.

My earlier reference to object lifetime was not by accident. What you
are trying to achieve appears to be some ownership model wherein a
contained object may survive its container.
If this is the goal, then it is better to make such choice explicit by
providing specific copy/move/reference accessors, instead of relying on
the semantics of temporaries. That is IMO, of course.

>
> Again, they'll work just fine when invoked as (*ptr).method(), (if
> appropriately overloaded) but not as ptr->method(). No way to make that
> work.

Indeed not, but then if ownership is allocated to std::unique_ptr, then
it is better to manage the entire object lifetime through such
unique_ptr, instead of splitting this responsibility between container
and contained.

Sam

unread,
May 26, 2020, 6:46:53 PM5/26/20
to
Manfred writes:

> My earlier reference to object lifetime was not by accident. What you are
> trying to achieve appears to be some ownership model wherein a contained
> object may survive its container.

Actually, it's the opposite: the contained object may not survive its
container.

And in case of std::unique_ptr, it's guaranteed not to survive its container.

> Indeed not, but then if ownership is allocated to std::unique_ptr, then it
> is better to manage the entire object lifetime through such unique_ptr,
> instead of splitting this responsibility between container and contained.

It is true that (in this context) one can explicitly assign the container,
and thus preserve ownership:

auto office=building.penthouse();

const auto &available_hours=office->get_available_hours();

Here, the returned value from build.penthouse() is assigned to "office". The
returned value happens to be a unique_ptr, but I don't really care. My
contract, is that penthouse() returns a container for an office of some kind
that implements get_available_hours() whose lifetime exists as long as the
object that implements get_available_hours() exists.

I can't do

const auto &available_hours=building.penthouse()->get_available_hours();

This will leave me with dangling reference. Instead I have to assign
whatever penthouse() returns to an auto, and then dereference it. And then
this auto object exists until the end of the scope too, which might have
other side effects, as well.

But suppose you could overload -> so that it ends up calling either an & or
an && overload in the contained object, depending upon whether the container
itself is also & or &&. Then what we can do now? Well:

const auto &available_hours=office->get_available_hours();

This still calls the regular & overload, which returns the same const reference,
to the avilable hours. But now:

const auto &available_hours=building.penthouse()->get_available_hours();

Much shorter, cleaner, and to the point. This uses the currently non-
existent mechanism to invoke the && overload, which returns a prvalue
instead of a const reference. Its lifetime now gets automatically extended,
as per the current rules of doing so.

Also: whatever object penthouse() returned has served its purpose, and can
go away, and it doesn't have to exist until the end of the scope.

But, to achieve the same results right now, one has to do
(*building.penthouse()).get_available_hours(), employing the operator*
overload in penthouse's returned object, which returns an && instead of &.
This is fugly.

Manfred

unread,
May 27, 2020, 9:36:58 AM5/27/20
to
On 5/27/2020 12:46 AM, Sam wrote:
> Manfred writes:
>
>> My earlier reference to object lifetime was not by accident. What you
>> are trying to achieve appears to be some ownership model wherein a
>> contained object may survive its container.
>
> Actually, it's the opposite: the contained object may not survive its
> container.

No it's not the opposite.
The container I am referring to is the "basket" of your example upthread:

> class apple {
>
> // .. something
> };
>
> class basket {
>
> private:
> apple fuji;
>
> public:
>
> const apple &getApple() const & { return fuji; }
>
> apple getApple() && { return fuji; }
> };

wherein you wish that

const auto &lunch=get_current_basket()->getApple();

yields an apple that survives the basket.

>
> And in case of std::unique_ptr, it's guaranteed not to survive its
> container.

std::unique_ptr is a smart pointer. I was not referring to it as a
container.

>
>> Indeed not, but then if ownership is allocated to std::unique_ptr,
>> then it is better to manage the entire object lifetime through such
>> unique_ptr, instead of splitting this responsibility between container
>> and contained.
>
> It is true that (in this context) one can explicitly assign the
> container, and thus preserve ownership:
>
> auto office=building.penthouse();
>
> const auto &available_hours=office->get_available_hours();
>
> Here, the returned value from build.penthouse() is assigned to "office".
> The returned value happens to be a unique_ptr, but I don't really care.
> My contract, is that penthouse() returns a container for an office of
> some kind that implements get_available_hours() whose lifetime exists as
> long as the object that implements get_available_hours() exists.

So, you want some available_hours object to be part of (contained
within) an office object, and still survive the office.

>
> I can't do
>
> const auto &available_hours=building.penthouse()->get_available_hours();
>
> This will leave me with dangling reference. Instead I have to assign
> whatever penthouse() returns to an auto, and then dereference it. And
> then this auto object exists until the end of the scope too, which might
> have other side effects, as well.
>
> But suppose you could overload -> so that it ends up calling either an &
> or an && overload in the contained object, depending upon whether the
> container itself is also & or &&. Then what we can do now? Well:
>
> const auto &available_hours=office->get_available_hours();
>
> This still calls the regular & overload, which returns the same const
> reference, to the avilable hours. But now:
>
> const auto &available_hours=building.penthouse()->get_available_hours();
>
> Much shorter, cleaner, and to the point. This uses the currently
> non-existent mechanism to invoke the && overload, which returns a
> prvalue instead of a const reference. Its lifetime now gets
> automatically extended, as per the current rules of doing so.
>
> Also: whatever object penthouse() returned has served its purpose, and
> can go away, and it doesn't have to exist until the end of the scope.

Right, but then if you want available_hours to be a self standing
object, why do you declare it with auto& ?

Also: I notice some possible overuse of auto for my taste here.

Sam

unread,
May 27, 2020, 10:21:16 PM5/27/20
to
Manfred writes:

>> const auto &available_hours=building.penthouse()->get_available_hours();
>>
>> Much shorter, cleaner, and to the point. This uses the currently non-
>> existent mechanism to invoke the && overload, which returns a prvalue
>> instead of a const reference. Its lifetime now gets automatically extended,
>> as per the current rules of doing so.
>>
>> Also: whatever object penthouse() returned has served its purpose, and can
>> go away, and it doesn't have to exist until the end of the scope.
>
> Right, but then if you want available_hours to be a self standing object,
> why do you declare it with auto& ?

If `building` ensures that whatever its `penthouse()` tells me its
available_hours exists as long as the `building` exists, then
get_available_hours() may itself return a reference that I'll stash away
here, without making a copy of it. In other words, the `available_hours` may
be something that's owned by the building, and `penthouse()` is just a means
to access it. Or, alternatively, in the current use case, `penthouse()`
itself has the ownership on the `available_hours`. It doesn't matter. If its
a temporary object this allows the same syntax to work by having the rvalue
overload for get_available_hours() return a prvalue, whose lifetime gets
extended by assigning it to a const reference.

By assigning the result to a const reference it makes it possible to make
small adjustments in the design of who owns what (within certain
boundaries), with minimal, if any, code changes.

It's something that's not very apparent, how ref qualifiers can work
together with temporaries' lifetime extension to result in quite flexible
code that can survive an otherwise major design change without really having
to change anything.

> Also: I notice some possible overuse of auto for my taste here.

Not the first time that aversion to the use of auto was vocalized, around
here. To each their own. I find auto to be a useful tool, that saved me a
lot of time.

Juha Nieminen

unread,
May 28, 2020, 2:10:13 AM5/28/20
to
Sam <s...@email-scan.com> wrote:
>> > The original question, that was axed
>>
>> Indeed, it was axed. Conversation over.
>
> I wasn't conversing with you in the first place.

Woosh.

Manfred

unread,
Jun 7, 2020, 11:54:57 AM6/7/20
to
On 5/28/2020 4:21 AM, Sam wrote:
> Manfred writes:
>
>>> const auto &available_hours=building.penthouse()->get_available_hours();
>>>
>>> Much shorter, cleaner, and to the point. This uses the currently
>>> non-existent mechanism to invoke the && overload, which returns a
>>> prvalue instead of a const reference. Its lifetime now gets
>>> automatically extended, as per the current rules of doing so.
>>>
>>> Also: whatever object penthouse() returned has served its purpose,
>>> and can go away, and it doesn't have to exist until the end of the
>>> scope.
>>
>> Right, but then if you want available_hours to be a self standing
>> object, why do you declare it with auto& ?
>
> If `building` ensures that whatever its `penthouse()` tells me its
> available_hours exists as long as the `building` exists, then
> get_available_hours() may itself return a reference that I'll stash away
> here, without making a copy of it. In other words, the `available_hours`
> may be something that's owned by the building, and `penthouse()` is just
> a means to access it. Or, alternatively, in the current use case,
> `penthouse()` itself has the ownership on the `available_hours`. It
> doesn't matter. If its a temporary object this allows the same syntax to
> work by having the rvalue overload for get_available_hours() return a
> prvalue, whose lifetime gets extended by assigning it to a const reference.
>
> By assigning the result to a const reference it makes it possible to
> make small adjustments in the design of who owns what (within certain
> boundaries), with minimal, if any, code changes.
>
> It's something that's not very apparent, how ref qualifiers can work
> together with temporaries' lifetime extension to result in quite
> flexible code that can survive an otherwise major design change without
> really having to change anything.

It is not very apparent because it is not what C++ has been designed for.
Deterministic control of objects' lifetime is an archetypal component of
C++ design, wherein the programmer has full control, and thus
responsibility, of object destruction.
It seems to me that what you are wishing for here is somewhat closer to
a garbage collecting environment wherein you only care of obtaining a
reference to an object and you delegate to the language its due destruction.

From this perspective it is no surprise that you don't find yourself
comfortable with the result.

Sam

unread,
Jun 7, 2020, 1:03:46 PM6/7/20
to
The lifetime of all of these objects is explicitly defined, and known.
There's nothing here that comes anywhere near garbage collection, unless
what smart pointers do is also considered to be garbage collection.


Manfred

unread,
Jun 7, 2020, 1:26:06 PM6/7/20
to
On 6/7/2020 7:03 PM, Sam wrote:
> Manfred writes:
>
>> On 5/28/2020 4:21 AM, Sam wrote:
>>> Manfred writes:
>>>
>>>>> const auto
>>>>> &available_hours=building.penthouse()->get_available_hours();
>>>>>
>>>>> Much shorter, cleaner, and to the point. This uses the currently
>>>>> non-existent mechanism to invoke the && overload, which returns a
I was not referring to smart pointers at all.

In your example you wrote your wish for a feature wherein "It doesn't
matter" who has ownership of the `available_hours` object - you point
out that using a properly qualified reference "the same syntax" should
work irrespective of where such ownership sits.

This is what GC is for: you get a reference to an object and you don't
care about its ownership: you let the language destroy it as soon as
(well, soon-ish) all of its references go out of scope.

However, in your follow-up you specify that "The lifetime of all of
these objects is explicitly defined, and known" - great, all I'm saying
is that this doesn't play well with your wish that "It doesn't matter"
where ownership is assigned.

As a cross reference, see about deterministic finalizers in garbage
collected languages, as an attempt to remove the "ish" from above.

Manfred

unread,
Jun 7, 2020, 1:36:53 PM6/7/20
to
On 6/7/2020 7:25 PM, Manfred wrote:
> This is what GC is for: you get a reference to an object and you don't
> care about its ownership: you let the language destroy it as soon as
> (well, soon-ish) all of its references go out of scope.
>
> However, in your follow-up you specify that "The lifetime of all of
> these objects is explicitly defined, and known" - great, all I'm saying
> is that this doesn't play well with your wish that "It doesn't matter"
> where ownership is assigned.

That said, I don't deny that it could be of interest that this could
indeed play well - attempts so far have reached mixed results, IMHO.

Sam

unread,
Jun 7, 2020, 9:53:42 PM6/7/20
to
Manfred writes:

> I was not referring to smart pointers at all.
>
> ...
>
> This is what GC is for: you get a reference to an object and you don't care
> about its ownership: you let the language destroy it as soon as (well, soon-
> ish) all of its references go out of scope.

Well, smart pointers definitely destroy the object when all references to
the object go out of scope. This seems to be perfectly compliant with this
particular definition of "garbage collection".

> However, in your follow-up you specify that "The lifetime of all of these
> objects is explicitly defined, and known" - great, all I'm saying is that
> this doesn't play well with your wish that "It doesn't matter" where
> ownership is assigned.

No, the two concepts do not conflict with each other.

Entity A produces a temporary object B which gives me access to object C. I
need B to access C.

I don't know whether C is owned by A or B. If it's owned by A, B can simply
give me a reference to C, before B vanishes in a puff of smoke. If C is
owned by B, B can return C directly, not a reference.

If the returned value from C is assigned to a reference, the ownership model
can be changed without changing the API. If B returns a reference it's
assigned to a reference, and that's it. If B returns an object, its lifetime
is extended to the lifetime of the reference.

In all cases, the lifetime of all objects is well defined, and does not
require garbage collection.

Öö Tiib

unread,
Jun 8, 2020, 2:25:34 AM6/8/20
to
On Monday, 8 June 2020 04:53:42 UTC+3, Sam wrote:
> Manfred writes:
>
> > I was not referring to smart pointers at all.
> >
> > ...
> >
> > This is what GC is for: you get a reference to an object and you don't care
> > about its ownership: you let the language destroy it as soon as (well, soon-
> > ish) all of its references go out of scope.
>
> Well, smart pointers definitely destroy the object when all references to
> the object go out of scope. This seems to be perfectly compliant with this
> particular definition of "garbage collection".
>
> > However, in your follow-up you specify that "The lifetime of all of these
> > objects is explicitly defined, and known" - great, all I'm saying is that
> > this doesn't play well with your wish that "It doesn't matter" where
> > ownership is assigned.
>
> No, the two concepts do not conflict with each other.
>
> Entity A produces a temporary object B which gives me access to object C. I
> need B to access C.
>
> I don't know whether C is owned by A or B. If it's owned by A, B can simply
> give me a reference to C, before B vanishes in a puff of smoke. If C is
> owned by B, B can return C directly, not a reference.

What is the resulting ownership? Does the entity that needs
"C" (you call it confusingly "I" and "me") have potentially shared
ownership of "C" with "A" or does it on all cases have full ownership
of passed "C"?

>
> If the returned value from C is assigned to a reference, the ownership model
> can be changed without changing the API. If B returns a reference it's
> assigned to a reference, and that's it. If B returns an object, its lifetime
> is extended to the lifetime of the reference.
>
> In all cases, the lifetime of all objects is well defined, and does not
> require garbage collection.

When ownership is unclear then lifetime of objects is also unclear and
needs garbage collection. Ownership model can not be dynamic or unclear,
it has to be something. Clarity of it is better also in garbage
collected languages. It may actually be somehow clear to you but for
whatever strange reasons you refused to discuss it rudely.


Sam

unread,
Jun 8, 2020, 9:35:21 PM6/8/20
to
Öö Tiib writes:

> >
> > Entity A produces a temporary object B which gives me access to object C. I
> > need B to access C.
> >
> > I don't know whether C is owned by A or B. If it's owned by A, B can simply
> > give me a reference to C, before B vanishes in a puff of smoke. If C is
> > owned by B, B can return C directly, not a reference.
>
> What is the resulting ownership? Does the entity that needs
> "C" (you call it confusingly "I" and "me") have potentially shared
> ownership of "C" with "A" or does it on all cases have full ownership
> of passed "C"?

I think I've explained the situation as clearly as I could. If you still
don't understand the basic concepts, then I can't think of any other way to
explain them. Especially when the interchangable usage of "I" and "me"
already introduces so much confusion.

> > If the returned value from C is assigned to a reference, the ownership
> model
> > can be changed without changing the API. If B returns a reference it's
> > assigned to a reference, and that's it. If B returns an object, its
> lifetime
> > is extended to the lifetime of the reference.
> >
> > In all cases, the lifetime of all objects is well defined, and does not
> > require garbage collection.
>
> When ownership is unclear then lifetime of objects is also unclear and

That's the whole purpose of smart pointers. As long as you have direct
ownership of a smart pointer, or whatever object is used as a means of
access, that is sufficient to establish the lifetime of the referenced
object for the duration of the access. As long as you have your own smart
pointer, it is crystal clear that the object exists. When you relinquish the
pointer, you may not have a clear idea any more whether the object still
exists, or not.

That's the entire idea.

That's what smart pointers are all about. I don't find that concept very
difficult to comprehend, personally.

> needs garbage collection. Ownership model can not be dynamic or unclear,
> it has to be something. Clarity of it is better also in garbage
> collected languages. It may actually be somehow clear to you but for
> whatever strange reasons you refused to discuss it rudely.

I humbly apologize for being rude and offending your sensisibilities, Your
Royal Highness. Please forgive me.

Öö Tiib

unread,
Jun 9, 2020, 3:37:51 AM6/9/20
to
On Tuesday, 9 June 2020 04:35:21 UTC+3, Sam wrote:
> Öö Tiib writes:
>
> > >
> > > Entity A produces a temporary object B which gives me access to object C. I
> > > need B to access C.
> > >
> > > I don't know whether C is owned by A or B. If it's owned by A, B can simply
> > > give me a reference to C, before B vanishes in a puff of smoke. If C is
> > > owned by B, B can return C directly, not a reference.
> >
> > What is the resulting ownership? Does the entity that needs
> > "C" (you call it confusingly "I" and "me") have potentially shared
> > ownership of "C" with "A" or does it on all cases have full ownership
> > of passed "C"?
>
> I think I've explained the situation as clearly as I could. If you still
> don't understand the basic concepts, then I can't think of any other way to
> explain them. Especially when the interchangable usage of "I" and "me"
> already introduces so much confusion.

Nope, it was very weird explanation! Other subjects that communicate in
forums use "I" and "me" during that communication to notion themselves.
Are you programming programs or participating in role of class instance
that for whatever reason needs "C" in run-time data hierarchies of C++
program? So what "you" do there? Do "you" have "C" under shared
ownership with that "A" or not?

>
> > > If the returned value from C is assigned to a reference, the ownership
> > model
> > > can be changed without changing the API. If B returns a reference it's
> > > assigned to a reference, and that's it. If B returns an object, its
> > lifetime
> > > is extended to the lifetime of the reference.
> > >
> > > In all cases, the lifetime of all objects is well defined, and does not
> > > require garbage collection.
> >
> > When ownership is unclear then lifetime of objects is also unclear and
>
> That's the whole purpose of smart pointers. As long as you have direct
> ownership of a smart pointer, or whatever object is used as a means of
> access, that is sufficient to establish the lifetime of the referenced
> object for the duration of the access. As long as you have your own smart
> pointer, it is crystal clear that the object exists. When you relinquish the
> pointer, you may not have a clear idea any more whether the object still
> exists, or not.
>
> That's the entire idea.
>
> That's what smart pointers are all about. I don't find that concept very
> difficult to comprehend, personally.

Smart pointers only help with life-time management of object instances.
These do not magically remove any concerns of run-time data hierarchy.
"Having" smart pointer instance does not make object's existence "crystal
clear" as smart pointers can be null like raw pointers.
If there is potentially shared ownership then there are additional
concerns of logical conflicts of usage of shared property (worst case
of whose is data race) plus risk to have directed cycles in data that
then stops the smart pointers from managing lifetime.

>
> > needs garbage collection. Ownership model can not be dynamic or unclear,
> > it has to be something. Clarity of it is better also in garbage
> > collected languages. It may actually be somehow clear to you but for
> > whatever strange reasons you refused to discuss it rudely.
>
> I humbly apologize for being rude and offending your sensisibilities, Your
> Royal Highness. Please forgive me.

There are no differences if to refuse discussing ownership models
clownishly or harshly.

Sal LO

unread,
Jun 10, 2020, 12:59:00 PM6/10/20
to
0 new messages