C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Safer API for minmax with friends?

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Thu, 6 Mar 2025 21:34:55 +0100
czw., 6 mar 2025 o 14:34 Jarrad Waterloo via Std-Proposals
<std-proposals_at_[hidden]> napisał(a):
>
> #1
>
> The proactive defensive programming kindness of API authors to those who use their API(s).
>
> When a cont& return is dependent upon a const& input parameter, in part or in whole, the input parameter can be changed to a const reference_wrapper of const which prevents one from using temporaries.
>
> const T& min( const reference_wrapper<const T> a, const reference_wrapper<const T> b );
>


I think we should make different solutions and make this explicit.
Add a new overload for `std::minmax` that is chosen only when one of
the arguments is
r-value and then issue an obsolete message. And add dummy cast that can be used
to avoid this obsolete message:

```
auto z = std::minmax(x, std::allow_dangling(2 + 5));
foo(std::minmax(std::allow_dangling(x + 5), std::allow_dangling(2 + 5))
```

Bit more verbose but if an alternative is UB code this could be
helpful in a long time.
This `std::allow_dangling` could be used in other cases when it is safe to use
references to temporales.

This overall preserves current functionality (and makes it easy to upgrade) and
reduces unexpected UB.



> #2
>
> Fix the language with temporary reduction.
>
> 2023
> variable scope
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2730r1.html
> C Dangling Reduction
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2750r2.html
> Reference checking
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2878r6.html
> 2022
> temporary storage class specifiers
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2658r1.html
>
> For those of you who have sacrificed the safety of the 99% for the 1% of temporary locks, consider this, those classes that need short lived temporary semantics could be annotated with [[temporary]]; problem solved.
>
> #3
>
> The problem is worse and more embarrassing than you know. What about when developers actually pass in constants to const parameters.
>
> ```c++
> auto [min, max] = std::minmax(37, 30 - 7);
> ```
>
> These dangle in the same way as before even though the compiler is more than capable of putting 30 and 30-7 into ROM. Since there are little to no ROM or const + static guarantees in the language than even this could, which should always work, fails and worse perplexes beginners.
>
> 2024
> const references to constexpr variables
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3218r0.html
> 2023
> constant dangling
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2724r1.html
> 2022
> implicit constant initialization
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2623r2.html
>
> The currently broken examples could be fixed with these proposals but for now you may have to liberally use some, yet to be accepted, C++26 features such as the following.
>
> 2024
> std::constexpr_wrapper
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2781r5.html
>
> ```c++
> auto [min, max] = std::minmax(std::cw<a>, std::cw<b - a>);// but only if a, b-a are constant expressions and there type is structural otherwise the CONSTANT macro works regardless
> ```
>
> 2025
> define_static_{string,object,array}
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3491r1.html
>
> ```c++
> auto [min, max] = std::minmax( *define_static_object(a), *define_static_object(b - a));// but only if a, b are structural otherwise the CONSTANT macro works regardless
> ```
>
> By the way those are explicit constant initialization. For the simpler implicit constant initialization see the following.
>
> Non-transient allocation with vector and basic_string
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3554r0.html
> There are many other recent proposals as well that would allow the original syntax.
> ```c++
> auto [min, max] = std::minmax(a, b - a);
> ```
>
> Summary i.e. the key take aways
> -------------
> 1) These simple solutions are easier than Rust because they don't require a noisy borrow collector klaxon warning. The compiler just makes it work with the knowledge it already knows.
> 2) implicit and explicit constant initialization does not break existing code and can be contributed back to C to make our shared community safer; it also makes our language simpler
> 3) fixing temporaries does not break existing code in C and depending upon the approach any C++ breakage can be mitigated significantly
> 4) These are low hanging fruit that can reduce the use after free of the stack which the Rust community has been pointing out even more than range access errors for the past decade. It is also the hardest memory bug for programmers because we have so few mitigations at present because it requires compiler support.
>
> ...
>
> NOTE: The opposite problem has been raised by the following.
>
> favor ease of use
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3326r0.html
>
>
>
> On Thu, Mar 6, 2025 at 5:27 AM Robin Savonen Söderholm via Std-Proposals <std-proposals_at_[hidden]> wrote:
>>
>> Hi!
>>
>> I recently got burned by a piece of innocent looking code that was something along the lines of this:
>> ```c++
>> auto [min, max] = std::minmax(a, b - a);
>> ```
>> The problem stems from the fact that std::minmax (not initialiser_list api) returns a std::pair<T const&, T const&> regardless of what the arguments are AND that the structural bindings 'auto' is just making sure that the object being separated is not a reference, NOT that the objects inside [...] are not references...
>> In the best of worlds I would argue that `auto [x, y] = ...` would mean that `x` and `y` are pure values, but I guess such a change could cause trouble due to breaking currently working code that relies on the reference capture (and it would complicate the meaning of `auto& [x, y] = ...`).
>> A simpler option would be to change the API for the minmax function (and perhaps other, similar looking functions) to something where if an RValue is detected amongst the arguments, it would return a std::pair<T, T> only (or pair<T const, T const>), while if all arguments are lvalue references it could keep it's current form (for speed or for the need of testing the address of an argument or something..)
>>
>> What would be the best remedy for things like this you think?
>> --
>> 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-03-06 20:35:08