Which brings us back to the idea that the caller is responsible for handling temporaries correctly (I think it was Yehezkel Bernat who raised it above with the example of std::min getting and returning const reference).

In those cases where you see that callers constantly do not handle temporaries correctly (the getOrDefault example by Louis Brandy) - then go and amend the function to handle temporaries differently, or just delete it for temporaries.

Bottom line: handling temporaries correctly is something that is unavoidable, whether you teach it or just count on the students to get to it by themselves at some point.

Is there something special - teaching-wise - for temporaries in range-based-for? maybe.
Can it be solved specifically for temporaries in range-based-for? maybe. But I'd argue it wouldn't make it easier to teach. Temporaries are still there with their weird behavior.
Not saying that the proposed fix is bad - it can be useful in eliminating potential bugs in range-based-for. But similar bugs in other, quite similar code examples, can still occur.


On Fri, Nov 13, 2020 at 1:25 PM Peter Sommerlad (C++) via SG20 <sg20@lists.isocpp.org> wrote:
FWIW

I would not delete the &&-qualified overload but return the string by
value to avoid leaking a dangling reference in such a case

std::string getName() && { return std::move(name); }

I think that would make your code work again. However, I am not sure,
this approach is generalizable for the standard library.

In addition we would need to provide rref-qualified overloads of
functions taking parameters by const-lref that return a reference to the
parameter (or its guts) as well.

std::string const & whatsYourName(Person const &p){ return p.getName(); }
std::string whatsYourName(Person &&p) { return p.getName(); } // rely on RVO

Regards
Peter.

Giuseppe D'Angelo via SG20 wrote on 13.11.20 11:20:
> On 13/11/2020 10:04, Amir Kirsh via SG20 wrote:
>>
>> Taking this approach in users' code is possible, though a bit
>> cumbersome today:
>>
>> class Person {
>>      std::string name;
>> public:
>>      Person(std::string name): name(std::move(name)) {}
>>      const std::string& getName() const & { return name; }
>>      const std::string& getName() const && = delete;
>
> This makes legitimate code not work:
>
> std::string name = // not a reference
>    getPerson().getName();
>
>
>>      // or:
>>      // std::string getName() const && { return name; }
>
> This works but makes a copy instead of moving the data member into the
> return object. You'd also need something like:
>
>    std::string getName() && { return std::move(name); }
>
>
> Usual disclaimer: you justify this because you know that std::string is
> reasonably cheap to move, so you can afford the cost of return by value
> in these circumstances. If you were to return something not necessarily
> cheap to move, then justifying the return by value -- even from rvalue
> overload -- is not so easy. So you'd return by rvalue reference
> (otherwise, a chain like getArrayOfPersons().getFirst().getName()
> creates N possibly expensive temporaries). And we're back to square one
> for the general case.
>
> Other usual disclaimer, this assumes that "moved from" means "partially
> formed", not "valid but unspecified" like the stdlib does (you've likely
> just broken class invariants).
>
> Moreover: going this route for any "getter" makes the implementation
> alone, today, terribly cumbersome (this is probably where "deducing
> this" could help). I still find that the teachability of all of this is
> nothing but dreadful.
>
> My 2 c,
>
>
>


--
Peter Sommerlad

Better Software: Consulting, Training, Reviews
Modern, Safe & Agile C++

peter.cpp@sommerlad.ch
+41 79 432 23 32
--
SG20 mailing list
SG20@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/sg20