Date: Fri, 25 Jul 2025 09:44:29 +0100
Jarrad and Tiago,
The both of you are still talking about [[must_store]], but in my
second or third post I moved onto [[lifetimebound]].
[[lifetimebound]] is suitable for stuff like "std::optional::value()"
or "wxCStr::operator wchar_t*()".
I'm not sure if [[must_store]] is useful at all. An example usage
would be to apply it to the return value from
"std::synchronized_value::rlock()". But I don't think it will give you
a compiler warning that you'd couldn't already get from
[[lifetimebound]].
On Fri, Jul 25, 2025 at 5:22 AM Tiago Freire via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> OP description is very clear. The only thing that is required to satisfy [[must_store]] is that it be assigned, that's it.
> It has nothing to do with the validity of an object B be bound to the lifetime of an object A.
> It might be what was intended but it is not what you get.
>
> Take the example.
> std::string get_name(int);
>
> Case A:
> char const* p = get_name(0).c_str();
> std::cout << p; // invalid use after free
>
> The OP now wants [[must_store]] to force you to do this:
> Case B:
> std::string temp = get_name(0);
> char const* p = temp.c_str();
> std::cout << p; // ok
>
> But you could just as easily have done this.
> Case C:
> char const* p;
> {
> std::string temp = get_name(0); //satisfies [[must_store]]
> p = temp.c_str();
> }
> std::cout << p; // invalid use after free
>
> Case C is virtually identical to Case A except you have given the temporary a name.
> It did not specify that the validity of p is bound to the lifetime of temp.
>
> Plus
> Case D:
> void fun(std::string const& temp)
> {
> char const* p = temp.c_str();
> std::cout << p; // ok
> }
>
> fun(get_name(0)); //does not satisfy [[must_store]]
>
> would be flagged as an error even though it is perfectly valid.
>
> If you want to specify that the validity of p is bound to the lifetime of temp, you would qualify the method "c_str()" that there's this relationship, you would not qualify that "get_name" "must be assigned" where the relationship between the 2 objects is not established.
>
>
> ________________________________
> From: Jarrad Waterloo <descender76_at_[hidden]>
> Sent: Friday, July 25, 2025 1:57:58 AM
> To: Tiago Freire <tmiguelf_at_[hidden]>
> Cc: std-proposals_at_[hidden] <std-proposals_at_[hidden]>
> Subject: Re: [std-proposals] Similar to [[no_discard]], but store in a variable, maybe call it [[must_store]]
>
> "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
>
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
The both of you are still talking about [[must_store]], but in my
second or third post I moved onto [[lifetimebound]].
[[lifetimebound]] is suitable for stuff like "std::optional::value()"
or "wxCStr::operator wchar_t*()".
I'm not sure if [[must_store]] is useful at all. An example usage
would be to apply it to the return value from
"std::synchronized_value::rlock()". But I don't think it will give you
a compiler warning that you'd couldn't already get from
[[lifetimebound]].
On Fri, Jul 25, 2025 at 5:22 AM Tiago Freire via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> OP description is very clear. The only thing that is required to satisfy [[must_store]] is that it be assigned, that's it.
> It has nothing to do with the validity of an object B be bound to the lifetime of an object A.
> It might be what was intended but it is not what you get.
>
> Take the example.
> std::string get_name(int);
>
> Case A:
> char const* p = get_name(0).c_str();
> std::cout << p; // invalid use after free
>
> The OP now wants [[must_store]] to force you to do this:
> Case B:
> std::string temp = get_name(0);
> char const* p = temp.c_str();
> std::cout << p; // ok
>
> But you could just as easily have done this.
> Case C:
> char const* p;
> {
> std::string temp = get_name(0); //satisfies [[must_store]]
> p = temp.c_str();
> }
> std::cout << p; // invalid use after free
>
> Case C is virtually identical to Case A except you have given the temporary a name.
> It did not specify that the validity of p is bound to the lifetime of temp.
>
> Plus
> Case D:
> void fun(std::string const& temp)
> {
> char const* p = temp.c_str();
> std::cout << p; // ok
> }
>
> fun(get_name(0)); //does not satisfy [[must_store]]
>
> would be flagged as an error even though it is perfectly valid.
>
> If you want to specify that the validity of p is bound to the lifetime of temp, you would qualify the method "c_str()" that there's this relationship, you would not qualify that "get_name" "must be assigned" where the relationship between the 2 objects is not established.
>
>
> ________________________________
> From: Jarrad Waterloo <descender76_at_[hidden]>
> Sent: Friday, July 25, 2025 1:57:58 AM
> To: Tiago Freire <tmiguelf_at_[hidden]>
> Cc: std-proposals_at_[hidden] <std-proposals_at_[hidden]>
> Subject: Re: [std-proposals] Similar to [[no_discard]], but store in a variable, maybe call it [[must_store]]
>
> "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
>
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2025-07-25 08:44:43