You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[QUESTION] How to make of inout parameters of type const T? #250
However, this raises some questions about inout and const parameters.
inout x: X means "present me an X I can read and write" and the function must contain a non-const use of X. But functions like std::min can't have write operations to its parameters because they're const reference. So cppfront should not give error diagnostics when there's no write operations to parameters that are inout and const.
I think this slight conflict of intentions could be confusing to beginners learning the language. Unless there's a better way, we could treat this as a special case of using inout and const for functions that MUST take parameters by const reference. I don't think there's many functions that do, I can only think of std::min, std::max, and std::clamp.
Would the Cpp2 way be use in parameters, and make them pointers, then return a pointer?
min: <T: type> (in a: T*, in b: T*) -> T* = {
if *a < *b {
return a;
}
else {
return b;
}
}
(I'm pretty sure my pointer syntax here is wrong...)
On 7 February 2023 09:52:12 Junyoung Lee ***@***.***> wrote:
I was thinking of how Cpp2 would implement std::min<https://en.cppreference.com/w/cpp/algorithm/min#Version_1>. Its signature looks like this:
template <typename T>
const T& min(const T& a, const T& b);
I came up with this answer (the template syntax is copied from #248 (comment)<#248 (comment)>:
min: <T: type> (inout a: const T, inout b: const T) -> forward const T = {
if a < b {
return a;
}
else {
return b;
}
}
https://godbolt.org/z/8Wa315arP
However, this raises some questions about inout and const parameters.
inout x: X means "present me an X I can read and write" and the function must contain a non-const use of X. But functions like std::min can't have write operations to its parameters because they're const reference. So cppfront should not give error diagnostics when there's no write operations to parameters that are inout and const.
I think this slight conflict of intentions could be confusing to beginners learning the language. Unless there's a better way, we could treat this as a special case of using inout and const for functions that MUST take parameters by const reference. I don't think there's many functions that do, I can only think of std::min, std::max, and std::clamp.
—
Reply to this email directly, view it on GitHub<#250>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQOEA6QRNXWUVYZLNTTWWILMRANCNFSM6AAAAAAUTXRYBY>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
Taking in by a pointer in this case looks like a work-around, we probably need a new way for this use case.
Off topic but I think template syntax could just have func : <T , U> ()= instead of func : <T : type, U : type> so that template arguments can default to typename and the function writer can specify otherwise.
min: (in a, in b) -> forward _ = {
if a < b {
return a;
}
else {
return b;
}
}
That will generate the following code:
[[nodiscard]]automin(autoconst& a, autoconst& b) -> decltype(auto){
if (cpp2::cmp_less(a,b)) {
return a;
}
else {
return b;
}
}
The general rule of cppfront that I have learned is to think about my intentions rather than what I expect generated code to look like.
In this example, if I want to read only from the variable, then it means I should use the in passing style. When you use inout passing style and don't write to the variable, cppfront will generate an error diagnostic to use in instead - check https://github.com/hsutter/708/blob/main/708.pdf (chapter 1.3.2 Inout parameters).
Please note that in passing style is the default so that you can write the min function as:
min: (a, b) -> forward _ = {
if a < b {
return a;
}
else {
return b;
}
}
inout x: X means "present me an X I can read and write"
const means "you can't write to it". So you cannot use inout with it. Simple as that.
"The function must contain a non-const use of X" is just the compiler being helpful and ensuring you're not making a mistake. Because you want it to be const, it can't be inout. It must be in, as @filipsajdak shows.
const means "you can't write to it". So you cannot use inout with it. Simple as that.
... which made me realize that const qualification doesn't make sense for parameters (except copy parameters, because only those are expressing the intent of really making a new variable... all the others are conceptually, and usually actually, aliases for the original argument).
So I should just disallow const for non-copy parameters... done, commit coming up...
Thanks again!
Reacted by Junyoung Lee, Filip Sajdak and Jorge Canizales
main.cpp2(3,9): error: a 'forward' return type cannot return an 'in' or 'move' parameter
main.cpp2(6,9): error: a 'forward' return type cannot return an 'in' or 'move' parameter
Here's why: #248 (comment).
Feels like the usual entanglement of lifetime and something else (Was it value category? The shape of the parameter?).
An attempt to provide safety that removes legit use-cases.
I feel like I remember now.
It feels like when a type author has a function that takes an reference to const:
structt {
voidf(const std::string&);
};
And in an attempt to be useful, they overload it with:
structt {
voidf(const std::string&);
voidf(std::string&&) = delete; // No, unsafe! Use the other overload.
};
But that's conflating lifetime and value category.
And it has unintended consequences or removes legit use-cases
(e.g. t().f(""), where the caller knows its safe).
I could suggest to revert the prohibition of forward returning an in parameter.
A templated min would work,
but a concrete min could return a reference to a local (see thread from #343 (comment)).
That relies on an implementation detail (the optimization done by for in parameters).
I could suggest for an in parameter to always lower to a Cpp1 reference
if the function's return type is by forward.
That's special-casing.
I could suggest to permit const-qualifying an inout parameter.
But out and inout are meant to be written to.
I really feel that the correct answer to support this use-case
is something like a reference (see thread from #343 (comment)).
I could suggest a new parameter direction, arg, that behaves like in/inout,
but is meant to be returned or stored.
So unlike in, object identity matters, so it's not optimized to pass by value.
And const is not implied, and can be specified.
But it overlaps a lot with in/inout.
I could suggest to revert the prohibition of forward returning an in parameter.
I argued for this some time back and still think it should be done.
I think the best way to view in parameters is to see them as const& which are sometimes optimised to copies if certain conditions are met. I don't think a new convention is needed (6 are already too much).
const means "you can't write to it". So you cannot use inout with it. Simple as that.
... which made me realize that const qualification doesn't make sense for parameters (except copy parameters, because only those are expressing the intent of really making a new variable... all the others are conceptually, and usually actually, aliases for the original argument).
in parameters are optimized, and the difference is observable (#686).
How about allowing inout _: const _ parameters to mean an in parameter with an address?
That way, we can implement std::min (concrete or generic) in Cpp2.
But don't you think that is is the kind of exceptions that cpp2 is trying to solve.
A simple solution would be to make in parameters always as const& when forward parameters are returned.
(But this MAY not cover all use cases, gotta think it out).
One way to look at the suggestion of allowing inout _: const _ parameters
is that out and inout allow modifying passed arguments, and thus alias.
By adding const to inout, we remove the modification aspect, but retain aliasing.
Another thought of mine is making all in parameters not alias if that was possible.
And to require the inout _: const _ spelling for otherwise aliasing in parameters.
Perhaps that way we can get a better default and more performance,
like Rust parameters as I once understood them (I think it was after watching a talk by David Sankel).
Rust parameters are faster because they're passed with restrict.
The model that cpp2 uses is superior in many ways, (hylo uses kinda close model).
If these kinda hacks are required, may as well fall back to references.
How about using pointers to indicate aliasing so we get performance AND safety by default when using the passing conventions?
I'm not sure I fully understand, but that was from what I understood.
Activity
SebastianTroy commentedon Feb 7, 2023
AbhinavK00 commentedon Feb 7, 2023
Taking in by a pointer in this case looks like a work-around, we probably need a new way for this use case.
Off topic but I think template syntax could just have
func : <T , U> ()=instead offunc : <T : type, U : type>so that template arguments can default to typename and the function writer can specify otherwise.filipsajdak commentedon Feb 7, 2023
@leejy12, what you try to do is probably:
min: (in a, in b) -> forward _ = { if a < b { return a; } else { return b; } }That will generate the following code:
The general rule of cppfront that I have learned is to think about my intentions rather than what I expect generated code to look like.
In this example, if I want to read only from the variable, then it means I should use the
inpassing style. When you useinoutpassing style and don't write to the variable, cppfront will generate an error diagnostic to useininstead - check https://github.com/hsutter/708/blob/main/708.pdf (chapter 1.3.2Inout parameters).Please note that
inpassing style is the default so that you can write the min function as:min: (a, b) -> forward _ = { if a < b { return a; } else { return b; } }jcanizales commentedon Feb 7, 2023
@leejy12 as you said:
constmeans "you can't write to it". So you cannot useinoutwith it. Simple as that."The function must contain a non-const use of X" is just the compiler being helpful and ensuring you're not making a mistake. Because you want it to be
const, it can't beinout. It must bein, as @filipsajdak shows.leejy12 commentedon Feb 7, 2023
Ah, I forgot about generic
_parameters. Thanks for your answers @filipsajdak @jcanizales !hsutter commentedon Mar 1, 2023
Yes, thanks to @leejy12 for raising the question...
and to @filipsajdak who wrote this...
and to @jcanizales who wrote this...
... which made me realize that
constqualification doesn't make sense for parameters (exceptcopyparameters, because only those are expressing the intent of really making a new variable... all the others are conceptually, and usually actually, aliases for the original argument).So I should just disallow
constfor non-copy parameters... done, commit coming up...Thanks again!
Disallow `const` qualification on parameter types other than `copy`, …
JohelEGP commentedon Apr 13, 2023
That no longer works: https://godbolt.org/z/fh45f1ecT.
JohelEGP commentedon May 22, 2023
Here's why: #248 (comment).
Feels like the usual entanglement of lifetime and something else (Was it value category? The shape of the parameter?).
An attempt to provide safety that removes legit use-cases.
JohelEGP commentedon May 22, 2023
I feel like I remember now.
It feels like when a type author has a function that takes an reference to
const:And in an attempt to be useful, they overload it with:
But that's conflating lifetime and value category.
And it has unintended consequences or removes legit use-cases
(e.g.
t().f(""), where the caller knows its safe).JohelEGP commentedon May 22, 2023
See https://quuxplusone.github.io/blog/2019/03/11/value-category-is-not-lifetime/.
JohelEGP commentedon May 22, 2023
I could suggest to revert the prohibition of
forwardreturning aninparameter.A templated
minwould work,but a concrete
mincould return a reference to a local (see thread from #343 (comment)).That relies on an implementation detail (the optimization done by for
inparameters).I could suggest for an
inparameter to always lower to a Cpp1 referenceif the function's return type is by
forward.That's special-casing.
I could suggest to permit
const-qualifying aninoutparameter.But
outandinoutare meant to be written to.I really feel that the correct answer to support this use-case
is something like a reference (see thread from #343 (comment)).
I could suggest a new parameter direction,
arg, that behaves likein/inout,but is meant to be returned or stored.
So unlike
in, object identity matters, so it's not optimized to pass by value.And
constis not implied, and can be specified.But it overlaps a lot with
in/inout.AbhinavK00 commentedon May 23, 2023
I argued for this some time back and still think it should be done.
I think the best way to view
inparameters is to see them asconst&which are sometimes optimised to copies if certain conditions are met. I don't think a new convention is needed (6 are already too much).JohelEGP commentedon May 23, 2023
I agree with that starting at #250 (comment).
But not as a way to implement
std::minbecauseJohelEGP commentedon Jun 9, 2023
This works, for now: https://cpp2.godbolt.org/z/rnKoEcaae.
Not for concrete
min.And someday even this example could be optimized like the concrete parameter
(in x: int).JohelEGP commentedon Sep 17, 2023
inparameters are optimized, and the difference is observable (#686).How about allowing
inout _: const _parameters to mean aninparameter with an address?That way, we can implement
std::min(concrete or generic) in Cpp2.AbhinavK00 commentedon Sep 17, 2023
But don't you think that is is the kind of exceptions that cpp2 is trying to solve.
A simple solution would be to make
inparameters always asconst&when forward parameters are returned.(But this MAY not cover all use cases, gotta think it out).
JohelEGP commentedon Sep 17, 2023
This made my think about parameter aliasing.
One way to look at the suggestion of allowing
inout _: const _parametersis that
outandinoutallow modifying passed arguments, and thus alias.By adding
consttoinout, we remove the modification aspect, but retain aliasing.Another thought of mine is making all
inparameters not alias if that was possible.And to require the
inout _: const _spelling for otherwise aliasinginparameters.Perhaps that way we can get a better default and more performance,
like Rust parameters as I once understood them (I think it was after watching a talk by David Sankel).
AbhinavK00 commentedon Sep 17, 2023
Rust parameters are faster because they're passed with
restrict.The model that cpp2 uses is superior in many ways, (hylo uses kinda close model).
I'm not sure I fully understand, but that was from what I understood.
Disallow `const` qualification on parameter types other than `copy`, …