C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Similar to [[no_discard]], but store in a variable, maybe call it [[must_store]]

From: Jarrad Waterloo <descender76_at_[hidden]>
Date: Thu, 24 Jul 2025 19:57:44 -0400
*"IMO the use case is completely bizarre."*

if [[must_store]] != [[lifetimebound]]

I do not know if it is bizarre or not. What are the differences and how do
those differences make it bizarre?

...

if [[must_store]] == [[lifetimebound]]

It is not bizarre? See below.




*"And with this you could also make technically valid code such as
this:"A(B(c));"to be flagged as invalid even though there's no way to tell
if it is."*

Scenario #1
Given
1) C& A([[lifetimebound]] C&);
2) C& B([[lifetimebound]] C&);

C& f()
{
    A(B(C{}));// OK reference is not bound to a local variable

    C& c1 = A(B(C{}));// ill formed, created a dangling reference bound to
a temporary whether it gets used or not
    c1.do_something();// ill formed, using a dangling reference bound to a
temporary

    if(regardless_of_percentage)
    {
        C& c2 = A(B(C{}));// ill formed, created a dangling reference
bound to a temporary whether it gets used or not
        return c2;// ill formed, returning a dangling reference bound to a
temporary
    }

    if(regardless_of_percentage)
    {
        return A(B(C{}));// ill formed, returning a dangling reference
bound to a temporary whether it gets used or not
    }

    C c3{};
    A(B(c3));// OK reference is not bound to a local variable
    C& c4 = A(B(c3));// OK but c4 ia a reference bound to a local
    return c4;// ill formed, returning a reference bound to a local
}

*"Why?"*

#1 for all of the dangling of locals and temporaries of the stack that this
identifies at compile time
#2 NOTE: This does NOT make valid code such as A(B(C{})); or A(B(c)); ill
formed.
#3 While references are a great starting point, this also works on constant
pointers and constant pointers to aggregate instances that contain non
constant pointers.
#4 More important than the pointer scenarios, when [[lifetimebound]] is
used with constructor parameters, then this logic also works on constant
instances of pointer and reference types; think const span, const
string_view.
#5 No noisy borrow checker, unique ownership, was needed to make a
significant dent in the dangling of the stack. Though this does not prevent
using unique ownership in the future.
#6 All analysis is local to the function
#7 BONUS: Should references to locals and temporaries be bound to the stack
of another thread or even the heap even for pedantic academic examples.
#8 using annotation now doesn't prevent using a RUST style core language
syntax a', b', c' in the future cause it is just good documentation that 2
major compilers already support.

Reference checking
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2878r6.html

On Thu, Jul 24, 2025 at 2:00 PM Tiago Freire <tmiguelf_at_[hidden]> wrote:

> IMO the use case is completely bizarre.
> And with this you could also make technically valid code such as this:
> A(B(c));
> to be flagged as invalid even though there's no way to tell if it is.
> Why?
>
> [[no_discard]] was introduced for interfaces that returned something, that
> could be easily be ignored, but because of what they do or how they work if
> you do discard it, you would almost certainly be doing something wrong.
> It poses no requirement that it need to be specifically assigned as long
> as you use it in some capacity, it is at least implied that you acknowledge
> that a return object exists and that you must do something about it.
> It doesn't tell you how to write your code, only that code must be written
> to deal with it.
>
> This is not that.
> All this does is force you to assign a name to it.
> If you completely ignore its existence after it has been assigned, this
> feature is completely fine with it.
> Which begs the question as to why it forced you to write code in a
> specific way, instead of adding anything useful?
>
> It's bizarre. What does it even achieve other than force you to write code
> in a very specific way?
>
>
> ------------------------------
> *From:* Std-Proposals <std-proposals-bounces_at_[hidden]> on behalf
> of Jarrad Waterloo via Std-Proposals <std-proposals_at_[hidden]>
> *Sent:* Thursday, July 24, 2025 5:39:58 PM
> *To:* std-proposals_at_[hidden] <std-proposals_at_[hidden]>
> *Cc:* Jarrad Waterloo <descender76_at_[hidden]>
> *Subject:* Re: [std-proposals] Similar to [[no_discard]], but store in a
> variable, maybe call it [[must_store]]
>
> It's also good documentation on how the function call should be used
> safely even if there is no static analyzer to enforce it.
>
> On Thu, Jul 24, 2025 at 11:18 AM Jeremy Rifkin via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> Why is an attribute needed for this? Why not rely on static analysis tools?
>
> Jeremy
>
> On Tue, Jul 22, 2025 at 06:05 Frederick Virchanza Gotham via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
>
> Sometimes we want the return value from a function not only to not be
> discarded, but more specifically we want it to be stored inside a
> local variable.
>
> Consider a method like std::synchronized_value::rlock, which returns
> by value a type similar to "std::lock_guard". We always want this
> return value to be stored in a variable:
>
> auto mylock = mysyncvalue.wlock();
>
> If the programmer is only going to call one method on it, then they
> should instead use "operator->", therefore we can consider the
> following to be incorrect:
>
> mysyncvalue.wlock()->DoSomething();
>
> and instead it should be replaced with:
>
> mysyncvalue->DoSomething();
>
> Therefore, it would make sense to mark the 'wlock' method as
> [[must_store]].
>
> Another example is the wxWidgets library, with the class wxString. The
> method, "wxString::c_str" looks like this:
>
> wxCStrData wxString::c_str() const
> {
> return wxCStrData(this);
> }
>
> When you invoke "c_str" on a wxString object, it returns a wxCStrData
> object. The wxCStrData class has two implicit conversions:
>
> operator const wchar_t*() const { return AsWChar(); }
> operator const char*() const { return AsChar(); }
>
> If you build the wxWidgets library in Unicode mode, then the method
> "AsWChar" returns a pointer to a pre-existing array. However if you
> build the wxWidgets library in ASCII mode, and then invoke "AsWChar",
> it dynamically allocates a temporary array and gives you the pointer.
> This could allow us to put a very interesting bug into our code:
>
> wxString mystr( wxS("Hello I'm a monkey") );
>
> wchar_t *const p = mystr.c_str();
>
> wcout << p << endl;
>
> The above code will work fine on Unicode, but might crash on ASCII.
> The dynamically-allocated array gets destroyed when "c_str()" returns.
> The solution is to do this:
>
> wxString mystr("Hello I'm a monkey");
>
> auto const converter = mystr.c_str();
> wchar_t *const p = converter;
>
> wcout << p << endl;
>
> If we can mark a function as [[must_store]] then it will prevent stuff
> like this from making its way into the release build.
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
>

Received on 2025-07-24 23:58:00