Date: Sat, 22 Mar 2025 13:46:54 +0100
sob., 22 mar 2025 o 10:17 Simon Schröder via Std-Proposals
<std-proposals_at_[hidden]> napisał(a):
>
> There are several distinct problems you want to solve with a single solution. For tow of the problems I rather see a different solution.
>
But was it not that a sign of a good solution is that it fixed a
couple of related problems at once?
For me the fundamental problem is that types that have reference
semantics (or keep hidden references) propagate too far unexpectedly.
And basic `auto` becomes an idiom for asking for value semantics (or
pointer/iterator semantics but then we need to explicitly use `*` or
`->`).
> 1. Expression templates: In the case of the Eigen library this is a lot more than just proxy objects. This enables lazy evaluation and optimization on chained matrix operations no compiler (not even Fortran) can do. Automatic decay would go contrary to this. I am personally not a fan of “always auto”. In this particular case using the explicit type instead of auto to force evaluation and avoid temporaries is IMHO the better solution. Together with deduction guides you don’t even have to write the template parameters.
>
Goal would be to decay only in specific places, not that it will decay
in the middle of expression on its own.
Overall if you use specific library than solution is simple, you use
`lib_eval()`, `lib_expression<>`, `lib_matrix` explicitly, but what if
you code is only
middleware of mathematical algorithms and try to by agnostic to end
user choose of expression template lib?
This is in some way similar to Herb `as/is` where a big family of
similar functions/operators (like `holds_alternative`, `any_cast`,
`has_value`, `dynamic_cast`) merge into two operators `as` and `is`.
Then in my case `auto{ value }` effectively becomes the "get rid of
references and give me simple value" operator, and allow less
cognitive overhead for all types that behave properly.
```
auto foo = a;
foo = b; // should this affect `a` in any way?
return foo; // should we worry about the lifetime of `a`?
```
For me in the best world on both we would have the answer "no" in all
cases (as types that can't do that would have `= delete` decay).
Expression templates are fundamental reference types as they need to
keep arguments around.
Forcing to use `auto&& foo = expr;` only improve it as us aware of
lifetime issue that could happens this code.
And as `&&` is linked to temporaries this make code more self documenting:
```
auto t = transpose(Vector1x2{ 1, 2 }); // decay, Vector2x1
auto&& arg1 = a * b; //no decay, it keep unevaluated
auto&& arg2 = c * d;
auto result = (t * arg1) * arg2; // decay, return vector
```
> I am personally not a fan of “always auto”
Could it be because it could easily hide pitfalls? But if all types
have pure value semantics this still would be a problem?
> 2. Proxies for container elements: I’m not sure what the standard for vector<bool> says. However, my understanding is that we can provide overloads on member functions with ref-qualifiers to avoid having proxies to temporaries. operator[](size_t)&& would return a copy instead of a proxy and operator[](size_t)& (with or without const) would still return a proxy.
>
> Solution 2 still does not solve your initial problem of the range-based for loop over vector<bool>. Again, if it is not too cumbersome, I would prefer the explicit type over auto. Another solution would be to use const iterators. Just use std::as_const on the container. Do const iterators on vector<bool> also return a proxy object? At least const_reference is defined as simply ‘bool’ (no ampersand!).
>
If I could choose I would not use `vector<bool>` or if I would indeed
then use explicit types or `as_const`. Problem is somewhere else, is
that I need to be always aware of types like
`vector<bool>` when my code can be used by others (or future me). This
is simple another thing that we are need to be aware of and guard
against it,
My goal is that code like
```
for (auto x : v) {}
```
Do the right thing or do not compile at all (because we try copying
millions of records as we try to iterate `Vector<Vector<int>>`).
> > On Mar 21, 2025, at 10:18 AM, Marcin Jaczewski via Std-Proposals <std-proposals_at_[hidden]> wrote:
> >
> > pt., 21 mar 2025 o 04:58 Jeremy Rifkin <rifkin.jer_at_[hidden]> napisał(a):
> >>
> >>> If I understand this correctly then this new decay operator will override glvalue-to-prvalue conversions
> >>
> >> That would cause tons of problems. It would effectively make your class no longer able to be used in most expressions.
> >
> > If done incorrect then yes, but technically this operator return
> > prvalue, as "default" look like:
> > ```
> > class Foo
> > {
> > Foo operator auto() = default;
> > };
> > ```
> > Isn't this effective glvalue-to-prvalue conversion?
> >
> >>
> >>> if `temp` is still the expression `a * b` then `+= temp` is an expression too that needs to be evaluated for every `x`.
> >>
> >> Correct, the expression template has to be deliberately evaluated at some point in your code. You seem to want to make this evaluation happen implicitly, which is a bad idea for all the same reasons array to pointer decay is bad. That would be subtle, surprising, and would interfere with most normal uses of expression templates (i.e., actually building the expression template).
> >>
> >>>> What would it return though?
> >>> MyTuple<int&, int&> minmax(int& a, int& b);
> >>
> >> So you'd create an entirely new tuple implementation just for this? That can't be good.
> >>
> >
> > Again, I do not plan to change `std` as this will break a lot of code.
> > This is for analogous functions that have better usability than `std`.
> >
> > Image "mystl" that starts from scratch and implements a more modern
> > version of types.
> > We already have cases with eastl or abseil or in some way boost.
> >
> > This tuple will not be used only in `minmax` but other functions too.
> >
> > Goal is that types have control over decay so that they could drop
> > reference semantics
> > on demand.
> >
> >> Cheers,
> >> Jeremy
> >>
> >>
> >>> On Thu, Mar 20, 2025 at 6:39 PM Marcin Jaczewski <marcinjaczewski86_at_[hidden]> wrote:
> >>>
> >>> pt., 21 mar 2025 o 01:01 Jeremy Rifkin <rifkin.jer_at_[hidden]> napisał(a):
> >>>>
> >>>>> Probably only in couple of relevant places of usage array-to-pointer we should
> >>>> put this user defined decay operator.
> >>>>
> >>>> It may be of interest to consider that nowhere in the standard does it directly say `auto x = arr;` undergoes "decay" or array-to-pointer conversion. This is a consequence of conversion sequences and there not being a glvalue-to-prvalue conversion for array types. The problem with what you want to propose is that class types *do* have glvalue-to-prvalue conversions.
> >>>>
> >>>
> >>> If I understand this correctly then this new decay operator will
> >>> override glvalue-to-prvalue conversions.
> >>> overall both happens in majority places next to each other is standard text.
> >>>
> >>>>>> What you're describing is a bug in the implementation of that expression template. I'm also not sure how decay is supposed to help that.
> >>>>> Its not a bug,
> >>>>
> >>>> The bug is trying to store a reference in the expression template and letting the user stumble into this footgun. It's also still not clear to me how decay is supposed to help here.
> >>>>
> >>>>> Now we have by orders of magnitude more operations
> >>>>
> >>>> I'm not following this argument
> >>>
> >>> if `temp` is still the expression `a * b` then `+= temp` is an
> >>> expression too that needs to be evaluated for every `x`.
> >>> We could write this as equivalent to more explicit code:
> >>>
> >>> ```
> >>> for (auto x : tab) x = eval(x + a * b);
> >>> ```
> >>>
> >>> And this needs multiple `a * b` each iteration. compare this to:
> >>>
> >>> ```
> >>> for (auto x : tab) x = eval(x + t);
> >>> ```
> >>>
> >>> where we need only add two matrices each iteration.
> >>>
> >>>>
> >>>>> This would be used in `std::minmax2` or `std2::` or your own classes
> >>>>
> >>>> What would it return though?
> >>>>
> >>>
> >>> ```
> >>> MyTuple<int&, int&> minmax(int& a, int& b);
> >>> ```
> >>> but after `auto x = minmax(a, b)`
> >>> type is `MyTuple<int, int> x` as it decayed.
> >>>
> >>>
> >>>> Cheers,
> >>>> Jeremy
> >>>>
> >>>>
> >>>> On Thu, Mar 20, 2025 at 5:25 PM Marcin Jaczewski <marcinjaczewski86_at_[hidden]> wrote:
> >>>>>
> >>>>> czw., 20 mar 2025 o 23:40 Jeremy Rifkin via Std-Proposals
> >>>>> <std-proposals_at_[hidden]> napisał(a):
> >>>>>>
> >>>>>> Hi,
> >>>>>>
> >>>>>> This idea would benefit from a lot of elaboration on exactly how this proposed decay is supposed to happen and when.
> >>>>>>
> >>>>>
> >>>>> Rule of thumb would be in same place where `int[5]` would decay to `int*`.
> >>>>> And this new operator would control `std::decay_t` result too.
> >>>>>
> >>>>> In my previous email I said that it should behave similar to
> >>>>> array-to-pointer conversion
> >>>>> but after checking standard text it has a bit more broader usage that
> >>>>> I would like.
> >>>>> Adding this to the user-defined conversion step would be asking for problems.
> >>>>> Probably only in couple of relevant places of usage array-to-pointer we should
> >>>>> put this user defined decay operator.
> >>>>>
> >>>>>>> Some consider this "bad feature" but for me this is more is "bad
> >>>>>> implementation" of "good feature".
> >>>>>>
> >>>>>> It's broadly considered a bad feature because implicit conversions can be subtle and surprising. This does not change with implementation.
> >>>>>>
> >>>>>>> I think that many user defined types could benefit from this functionality.
> >>>>>>> Proxy objects
> >>>>>>
> >>>>>> Can you clarify how you think some sort of user-specified decay helps the proxy object case? That already works fine as-is.
> >>>>>
> >>>>> Problem with proxy is when passed as value it still behaves as reference type.
> >>>>> This means it can dangle or update spruce unexpectedly.
> >>>>> And there is no generic way to prevent it, if we could use decay then it become
> >>>>> near generic operation of getting rid of references and proxy objects.
> >>>>> Consider this code:
> >>>>>
> >>>>> ```
> >>>>> assert(x != y);
> >>>>> auto a = x;
> >>>>> a = y;
> >>>>> assert(x != y); //ups... proxy
> >>>>>
> >>>>> auto b = temp()[10]; //only proxy is dangling
> >>>>> ```
> >>>>>
> >>>>> and:
> >>>>>
> >>>>> ```
> >>>>> assert(x != y);
> >>>>> auto& a = x;
> >>>>> a = y;
> >>>>> assert(x == y); //ok for majority of types
> >>>>>
> >>>>> auto& b = temp()[10]; //always dangling
> >>>>> ```
> >>>>>
> >>>>> with this decay proxies will have limited range it will unexpectedly
> >>>>> propagate as values,
> >>>>> and only by using `&` you can expand its reach and this has benefits
> >>>>> for being more visible.
> >>>>>
> >>>>>>
> >>>>>>> Expression templates
> >>>>>>
> >>>>>> What you're describing is a bug in the implementation of that expression template. I'm also not sure how decay is supposed to help that.
> >>>>>
> >>>>> Its not a bug, alternatively it would be forced to copy all temporary
> >>>>> arguments even if it will be discarded at the end of expression.
> >>>>> Another problem is evaluation as same expression can be reused in
> >>>>> couple of places:
> >>>>>
> >>>>> ```
> >>>>> auto temp = a * b;
> >>>>> for (auto x : tab) x += temp;
> >>>>> ```
> >>>>>
> >>>>> Now we have by orders of magnitude more operations. If expressio can
> >>>>> decay to a regular matrix then we have only simple
> >>>>> addition in the loop like if we used plain floats.
> >>>>>
> >>>>>>
> >>>>>>> Recent problem discussed on this mailing list with ref types
> >>>>>>
> >>>>>> It's not clear to me how decay is supposed to change that, especially without breaking code. This is just an unfortunate consequence of the design of std::minmax.
> >>>>>
> >>>>> But if my `Tuple` have decay and if I would implement `minmax` using
> >>>>> it then original poster would not have this problem:
> >>>>>
> >>>>> ```
> >>>>> auto& [x, y] = std::minmax(foo, bar);
> >>>>> //x and y are members from `Tuple<Foo&, Bar&>&` pointing to `foo` and `bar`
> >>>>>
> >>>>> auto [x, y] = std::minmax(foo, bar);
> >>>>> //x and y are value members from `Tuple<Foo, Bar>`
> >>>>> ```
> >>>>>
> >>>>> And I do not ask for changing `std::tuple` as this would be indead
> >>>>> breaking change,
> >>>>> This would be used in `std::minmax2` or `std2::` or your own classes.
> >>>>>
> >>>>>>
> >>>>>> Cheers,
> >>>>>> Jeremy
> >>>>>>
> >>>>>>
> >>>>>> On Thu, Mar 20, 2025 at 10:16 AM Marcin Jaczewski via Std-Proposals <std-proposals_at_[hidden]> wrote:
> >>>>>>>
> >>>>>>> Right now the only type that undergoes decaying is C array (aside of
> >>>>>>> refs or bit fields).
> >>>>>>> Some consider this "bad feature" but for me this is more is "bad
> >>>>>>> implementation" of "good feature".
> >>>>>>>
> >>>>>>> I think that many user defined types could benefit from this functionality.
> >>>>>>>
> >>>>>>> Proxy objects:
> >>>>>>> ```
> >>>>>>> std::vector<bool> ff = { false, false };
> >>>>>>> for (auto b : ff)
> >>>>>>> {
> >>>>>>> b = true;
> >>>>>>> }
> >>>>>>> assert(ff[0] == true);
> >>>>>>> ```
> >>>>>>>
> >>>>>>> Expression templates:
> >>>>>>> ```
> >>>>>>> auto matrix = Matrix2x2{1, 2, 3, 4}
> >>>>>>> auto mul = matrix * Vector2{3, 4};
> >>>>>>> Vector2 v = mul + Vector2{1, 1}; //UB because `mul` is expression and
> >>>>>>> dangle temporal vector
> >>>>>>> ```
> >>>>>>>
> >>>>>>> Recent problem discussed on this mailing list with ref types:
> >>>>>>>
> >>>>>>> ```
> >>>>>>> auto [a, b] = std::minmax(foo, 10);
> >>>>>>> auto c = a; // UB if `a` refer to `10`
> >>>>>>> ```
> >>>>>>>
> >>>>>>> Another could be a safer version of C array that is not copyable like
> >>>>>>> `std::array`
> >>>>>>> and instead of decay to pointer, it will decay to `std::span`.
> >>>>>>>
> >>>>>>> And finally if someone desires to ban "Almost Always Auto" as it could
> >>>>>>> be useful for
> >>>>>>> very heavy types for unexpected copies in generic code (by setting `= delete`).
> >>>>>>>
> >>>>>>> ```
> >>>>>>> auto x = a; //error
> >>>>>>> auto& x = a; //ok, no decay
> >>>>>>> NestedVector x = a; //ok
> >>>>>>> auto x = NestedVector{ a }; //ok
> >>>>>>> ```
> >>>>>>>
> >>>>>>> Code that enable it would look like:
> >>>>>>>
> >>>>>>> ```
> >>>>>>> struct Foo
> >>>>>>> {
> >>>>>>> Bar operator auto()
> >>>>>>> {
> >>>>>>> return Bar{};
> >>>>>>> }
> >>>>>>> };
> >>>>>>>
> >>>>>>> struct auto_ptr //pre C++11 unique_ptr
> >>>>>>> {
> >>>>>>> //nothing change but in places where it could be used diagnostic is raised
> >>>>>>> [[obsolete]] auto_ptr operator auto() = default;
> >>>>>>> };
> >>>>>>>
> >>>>>>> struct MyArray
> >>>>>>> {
> >>>>>>> //ban decay from temp objects like return from functions.
> >>>>>>> std::span<int> operator auto() && = deleted;
> >>>>>>> std::span<int> operator auto();
> >>>>>>> };
> >>>>>>>
> >>>>>>> template<typename T1, typename T2>
> >>>>>>> struct MyTuple
> >>>>>>> {
> >>>>>>> T1 t1;
> >>>>>>> T2 t2;
> >>>>>>> //decay propagate to members, making ref tuple safer to use if
> >>>>>>> returned form `std::minmax`
> >>>>>>> MyTuple<std::decay_t<T1>, std::decay_t<T2>> operator auto() {
> >>>>>>> return { t1, t2 }; }
> >>>>>>> };
> >>>>>>> ```
> >>>>>>>
> >>>>>>> When a type has multiple `operator auto` then all versions need to
> >>>>>>> return the same type.
> >>>>>>> Additionally, the returned type need not have a decay operator (or its
> >>>>>>> `=default`).
> >>>>>>>
> >>>>>>> Overall this make `auto x =` generally more safe to use and more regular.
> >>>>>>> --
> >>>>>>> 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
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
<std-proposals_at_[hidden]> napisał(a):
>
> There are several distinct problems you want to solve with a single solution. For tow of the problems I rather see a different solution.
>
But was it not that a sign of a good solution is that it fixed a
couple of related problems at once?
For me the fundamental problem is that types that have reference
semantics (or keep hidden references) propagate too far unexpectedly.
And basic `auto` becomes an idiom for asking for value semantics (or
pointer/iterator semantics but then we need to explicitly use `*` or
`->`).
> 1. Expression templates: In the case of the Eigen library this is a lot more than just proxy objects. This enables lazy evaluation and optimization on chained matrix operations no compiler (not even Fortran) can do. Automatic decay would go contrary to this. I am personally not a fan of “always auto”. In this particular case using the explicit type instead of auto to force evaluation and avoid temporaries is IMHO the better solution. Together with deduction guides you don’t even have to write the template parameters.
>
Goal would be to decay only in specific places, not that it will decay
in the middle of expression on its own.
Overall if you use specific library than solution is simple, you use
`lib_eval()`, `lib_expression<>`, `lib_matrix` explicitly, but what if
you code is only
middleware of mathematical algorithms and try to by agnostic to end
user choose of expression template lib?
This is in some way similar to Herb `as/is` where a big family of
similar functions/operators (like `holds_alternative`, `any_cast`,
`has_value`, `dynamic_cast`) merge into two operators `as` and `is`.
Then in my case `auto{ value }` effectively becomes the "get rid of
references and give me simple value" operator, and allow less
cognitive overhead for all types that behave properly.
```
auto foo = a;
foo = b; // should this affect `a` in any way?
return foo; // should we worry about the lifetime of `a`?
```
For me in the best world on both we would have the answer "no" in all
cases (as types that can't do that would have `= delete` decay).
Expression templates are fundamental reference types as they need to
keep arguments around.
Forcing to use `auto&& foo = expr;` only improve it as us aware of
lifetime issue that could happens this code.
And as `&&` is linked to temporaries this make code more self documenting:
```
auto t = transpose(Vector1x2{ 1, 2 }); // decay, Vector2x1
auto&& arg1 = a * b; //no decay, it keep unevaluated
auto&& arg2 = c * d;
auto result = (t * arg1) * arg2; // decay, return vector
```
> I am personally not a fan of “always auto”
Could it be because it could easily hide pitfalls? But if all types
have pure value semantics this still would be a problem?
> 2. Proxies for container elements: I’m not sure what the standard for vector<bool> says. However, my understanding is that we can provide overloads on member functions with ref-qualifiers to avoid having proxies to temporaries. operator[](size_t)&& would return a copy instead of a proxy and operator[](size_t)& (with or without const) would still return a proxy.
>
> Solution 2 still does not solve your initial problem of the range-based for loop over vector<bool>. Again, if it is not too cumbersome, I would prefer the explicit type over auto. Another solution would be to use const iterators. Just use std::as_const on the container. Do const iterators on vector<bool> also return a proxy object? At least const_reference is defined as simply ‘bool’ (no ampersand!).
>
If I could choose I would not use `vector<bool>` or if I would indeed
then use explicit types or `as_const`. Problem is somewhere else, is
that I need to be always aware of types like
`vector<bool>` when my code can be used by others (or future me). This
is simple another thing that we are need to be aware of and guard
against it,
My goal is that code like
```
for (auto x : v) {}
```
Do the right thing or do not compile at all (because we try copying
millions of records as we try to iterate `Vector<Vector<int>>`).
> > On Mar 21, 2025, at 10:18 AM, Marcin Jaczewski via Std-Proposals <std-proposals_at_[hidden]> wrote:
> >
> > pt., 21 mar 2025 o 04:58 Jeremy Rifkin <rifkin.jer_at_[hidden]> napisał(a):
> >>
> >>> If I understand this correctly then this new decay operator will override glvalue-to-prvalue conversions
> >>
> >> That would cause tons of problems. It would effectively make your class no longer able to be used in most expressions.
> >
> > If done incorrect then yes, but technically this operator return
> > prvalue, as "default" look like:
> > ```
> > class Foo
> > {
> > Foo operator auto() = default;
> > };
> > ```
> > Isn't this effective glvalue-to-prvalue conversion?
> >
> >>
> >>> if `temp` is still the expression `a * b` then `+= temp` is an expression too that needs to be evaluated for every `x`.
> >>
> >> Correct, the expression template has to be deliberately evaluated at some point in your code. You seem to want to make this evaluation happen implicitly, which is a bad idea for all the same reasons array to pointer decay is bad. That would be subtle, surprising, and would interfere with most normal uses of expression templates (i.e., actually building the expression template).
> >>
> >>>> What would it return though?
> >>> MyTuple<int&, int&> minmax(int& a, int& b);
> >>
> >> So you'd create an entirely new tuple implementation just for this? That can't be good.
> >>
> >
> > Again, I do not plan to change `std` as this will break a lot of code.
> > This is for analogous functions that have better usability than `std`.
> >
> > Image "mystl" that starts from scratch and implements a more modern
> > version of types.
> > We already have cases with eastl or abseil or in some way boost.
> >
> > This tuple will not be used only in `minmax` but other functions too.
> >
> > Goal is that types have control over decay so that they could drop
> > reference semantics
> > on demand.
> >
> >> Cheers,
> >> Jeremy
> >>
> >>
> >>> On Thu, Mar 20, 2025 at 6:39 PM Marcin Jaczewski <marcinjaczewski86_at_[hidden]> wrote:
> >>>
> >>> pt., 21 mar 2025 o 01:01 Jeremy Rifkin <rifkin.jer_at_[hidden]> napisał(a):
> >>>>
> >>>>> Probably only in couple of relevant places of usage array-to-pointer we should
> >>>> put this user defined decay operator.
> >>>>
> >>>> It may be of interest to consider that nowhere in the standard does it directly say `auto x = arr;` undergoes "decay" or array-to-pointer conversion. This is a consequence of conversion sequences and there not being a glvalue-to-prvalue conversion for array types. The problem with what you want to propose is that class types *do* have glvalue-to-prvalue conversions.
> >>>>
> >>>
> >>> If I understand this correctly then this new decay operator will
> >>> override glvalue-to-prvalue conversions.
> >>> overall both happens in majority places next to each other is standard text.
> >>>
> >>>>>> What you're describing is a bug in the implementation of that expression template. I'm also not sure how decay is supposed to help that.
> >>>>> Its not a bug,
> >>>>
> >>>> The bug is trying to store a reference in the expression template and letting the user stumble into this footgun. It's also still not clear to me how decay is supposed to help here.
> >>>>
> >>>>> Now we have by orders of magnitude more operations
> >>>>
> >>>> I'm not following this argument
> >>>
> >>> if `temp` is still the expression `a * b` then `+= temp` is an
> >>> expression too that needs to be evaluated for every `x`.
> >>> We could write this as equivalent to more explicit code:
> >>>
> >>> ```
> >>> for (auto x : tab) x = eval(x + a * b);
> >>> ```
> >>>
> >>> And this needs multiple `a * b` each iteration. compare this to:
> >>>
> >>> ```
> >>> for (auto x : tab) x = eval(x + t);
> >>> ```
> >>>
> >>> where we need only add two matrices each iteration.
> >>>
> >>>>
> >>>>> This would be used in `std::minmax2` or `std2::` or your own classes
> >>>>
> >>>> What would it return though?
> >>>>
> >>>
> >>> ```
> >>> MyTuple<int&, int&> minmax(int& a, int& b);
> >>> ```
> >>> but after `auto x = minmax(a, b)`
> >>> type is `MyTuple<int, int> x` as it decayed.
> >>>
> >>>
> >>>> Cheers,
> >>>> Jeremy
> >>>>
> >>>>
> >>>> On Thu, Mar 20, 2025 at 5:25 PM Marcin Jaczewski <marcinjaczewski86_at_[hidden]> wrote:
> >>>>>
> >>>>> czw., 20 mar 2025 o 23:40 Jeremy Rifkin via Std-Proposals
> >>>>> <std-proposals_at_[hidden]> napisał(a):
> >>>>>>
> >>>>>> Hi,
> >>>>>>
> >>>>>> This idea would benefit from a lot of elaboration on exactly how this proposed decay is supposed to happen and when.
> >>>>>>
> >>>>>
> >>>>> Rule of thumb would be in same place where `int[5]` would decay to `int*`.
> >>>>> And this new operator would control `std::decay_t` result too.
> >>>>>
> >>>>> In my previous email I said that it should behave similar to
> >>>>> array-to-pointer conversion
> >>>>> but after checking standard text it has a bit more broader usage that
> >>>>> I would like.
> >>>>> Adding this to the user-defined conversion step would be asking for problems.
> >>>>> Probably only in couple of relevant places of usage array-to-pointer we should
> >>>>> put this user defined decay operator.
> >>>>>
> >>>>>>> Some consider this "bad feature" but for me this is more is "bad
> >>>>>> implementation" of "good feature".
> >>>>>>
> >>>>>> It's broadly considered a bad feature because implicit conversions can be subtle and surprising. This does not change with implementation.
> >>>>>>
> >>>>>>> I think that many user defined types could benefit from this functionality.
> >>>>>>> Proxy objects
> >>>>>>
> >>>>>> Can you clarify how you think some sort of user-specified decay helps the proxy object case? That already works fine as-is.
> >>>>>
> >>>>> Problem with proxy is when passed as value it still behaves as reference type.
> >>>>> This means it can dangle or update spruce unexpectedly.
> >>>>> And there is no generic way to prevent it, if we could use decay then it become
> >>>>> near generic operation of getting rid of references and proxy objects.
> >>>>> Consider this code:
> >>>>>
> >>>>> ```
> >>>>> assert(x != y);
> >>>>> auto a = x;
> >>>>> a = y;
> >>>>> assert(x != y); //ups... proxy
> >>>>>
> >>>>> auto b = temp()[10]; //only proxy is dangling
> >>>>> ```
> >>>>>
> >>>>> and:
> >>>>>
> >>>>> ```
> >>>>> assert(x != y);
> >>>>> auto& a = x;
> >>>>> a = y;
> >>>>> assert(x == y); //ok for majority of types
> >>>>>
> >>>>> auto& b = temp()[10]; //always dangling
> >>>>> ```
> >>>>>
> >>>>> with this decay proxies will have limited range it will unexpectedly
> >>>>> propagate as values,
> >>>>> and only by using `&` you can expand its reach and this has benefits
> >>>>> for being more visible.
> >>>>>
> >>>>>>
> >>>>>>> Expression templates
> >>>>>>
> >>>>>> What you're describing is a bug in the implementation of that expression template. I'm also not sure how decay is supposed to help that.
> >>>>>
> >>>>> Its not a bug, alternatively it would be forced to copy all temporary
> >>>>> arguments even if it will be discarded at the end of expression.
> >>>>> Another problem is evaluation as same expression can be reused in
> >>>>> couple of places:
> >>>>>
> >>>>> ```
> >>>>> auto temp = a * b;
> >>>>> for (auto x : tab) x += temp;
> >>>>> ```
> >>>>>
> >>>>> Now we have by orders of magnitude more operations. If expressio can
> >>>>> decay to a regular matrix then we have only simple
> >>>>> addition in the loop like if we used plain floats.
> >>>>>
> >>>>>>
> >>>>>>> Recent problem discussed on this mailing list with ref types
> >>>>>>
> >>>>>> It's not clear to me how decay is supposed to change that, especially without breaking code. This is just an unfortunate consequence of the design of std::minmax.
> >>>>>
> >>>>> But if my `Tuple` have decay and if I would implement `minmax` using
> >>>>> it then original poster would not have this problem:
> >>>>>
> >>>>> ```
> >>>>> auto& [x, y] = std::minmax(foo, bar);
> >>>>> //x and y are members from `Tuple<Foo&, Bar&>&` pointing to `foo` and `bar`
> >>>>>
> >>>>> auto [x, y] = std::minmax(foo, bar);
> >>>>> //x and y are value members from `Tuple<Foo, Bar>`
> >>>>> ```
> >>>>>
> >>>>> And I do not ask for changing `std::tuple` as this would be indead
> >>>>> breaking change,
> >>>>> This would be used in `std::minmax2` or `std2::` or your own classes.
> >>>>>
> >>>>>>
> >>>>>> Cheers,
> >>>>>> Jeremy
> >>>>>>
> >>>>>>
> >>>>>> On Thu, Mar 20, 2025 at 10:16 AM Marcin Jaczewski via Std-Proposals <std-proposals_at_[hidden]> wrote:
> >>>>>>>
> >>>>>>> Right now the only type that undergoes decaying is C array (aside of
> >>>>>>> refs or bit fields).
> >>>>>>> Some consider this "bad feature" but for me this is more is "bad
> >>>>>>> implementation" of "good feature".
> >>>>>>>
> >>>>>>> I think that many user defined types could benefit from this functionality.
> >>>>>>>
> >>>>>>> Proxy objects:
> >>>>>>> ```
> >>>>>>> std::vector<bool> ff = { false, false };
> >>>>>>> for (auto b : ff)
> >>>>>>> {
> >>>>>>> b = true;
> >>>>>>> }
> >>>>>>> assert(ff[0] == true);
> >>>>>>> ```
> >>>>>>>
> >>>>>>> Expression templates:
> >>>>>>> ```
> >>>>>>> auto matrix = Matrix2x2{1, 2, 3, 4}
> >>>>>>> auto mul = matrix * Vector2{3, 4};
> >>>>>>> Vector2 v = mul + Vector2{1, 1}; //UB because `mul` is expression and
> >>>>>>> dangle temporal vector
> >>>>>>> ```
> >>>>>>>
> >>>>>>> Recent problem discussed on this mailing list with ref types:
> >>>>>>>
> >>>>>>> ```
> >>>>>>> auto [a, b] = std::minmax(foo, 10);
> >>>>>>> auto c = a; // UB if `a` refer to `10`
> >>>>>>> ```
> >>>>>>>
> >>>>>>> Another could be a safer version of C array that is not copyable like
> >>>>>>> `std::array`
> >>>>>>> and instead of decay to pointer, it will decay to `std::span`.
> >>>>>>>
> >>>>>>> And finally if someone desires to ban "Almost Always Auto" as it could
> >>>>>>> be useful for
> >>>>>>> very heavy types for unexpected copies in generic code (by setting `= delete`).
> >>>>>>>
> >>>>>>> ```
> >>>>>>> auto x = a; //error
> >>>>>>> auto& x = a; //ok, no decay
> >>>>>>> NestedVector x = a; //ok
> >>>>>>> auto x = NestedVector{ a }; //ok
> >>>>>>> ```
> >>>>>>>
> >>>>>>> Code that enable it would look like:
> >>>>>>>
> >>>>>>> ```
> >>>>>>> struct Foo
> >>>>>>> {
> >>>>>>> Bar operator auto()
> >>>>>>> {
> >>>>>>> return Bar{};
> >>>>>>> }
> >>>>>>> };
> >>>>>>>
> >>>>>>> struct auto_ptr //pre C++11 unique_ptr
> >>>>>>> {
> >>>>>>> //nothing change but in places where it could be used diagnostic is raised
> >>>>>>> [[obsolete]] auto_ptr operator auto() = default;
> >>>>>>> };
> >>>>>>>
> >>>>>>> struct MyArray
> >>>>>>> {
> >>>>>>> //ban decay from temp objects like return from functions.
> >>>>>>> std::span<int> operator auto() && = deleted;
> >>>>>>> std::span<int> operator auto();
> >>>>>>> };
> >>>>>>>
> >>>>>>> template<typename T1, typename T2>
> >>>>>>> struct MyTuple
> >>>>>>> {
> >>>>>>> T1 t1;
> >>>>>>> T2 t2;
> >>>>>>> //decay propagate to members, making ref tuple safer to use if
> >>>>>>> returned form `std::minmax`
> >>>>>>> MyTuple<std::decay_t<T1>, std::decay_t<T2>> operator auto() {
> >>>>>>> return { t1, t2 }; }
> >>>>>>> };
> >>>>>>> ```
> >>>>>>>
> >>>>>>> When a type has multiple `operator auto` then all versions need to
> >>>>>>> return the same type.
> >>>>>>> Additionally, the returned type need not have a decay operator (or its
> >>>>>>> `=default`).
> >>>>>>>
> >>>>>>> Overall this make `auto x =` generally more safe to use and more regular.
> >>>>>>> --
> >>>>>>> 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
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2025-03-22 12:47:08