On Thursday, 10 March 2022 at 07:21:23 UTC, bauss wrote:
> On Wednesday, 9 March 2022 at 19:02:27 UTC, Timon Gehr wrote:
> On 3/9/22 11:04, Quirin Schroll wrote:
> In the changelog it says:
> When used inside a foreach using an overloaded opApply
, the trait yields the parameters to the delegate and not the function the foreach appears within.
Why? This will rarely be wanted or be used intentionally. When a programmer uses __traits(parameters)
in a foreach
loop, it will for certain happen to someone not aware of this. The iteration implemention (opApply
or range functions) is a detail one should not really have to care about on the usage side. This complicates the language. Morally, this is a bug. Please reconsider this design decision before it sticks.
It's a consequence of how such a foreach loop is lowered. Looks like one of those cases where a bug was resolved by changing the specification.
Surely we could work around it?
The issue is not the complexity of the workaround (example below), but that it is in practice hard to know when it applies and it is hard to teach (cf. Scott Meyers: Language is good == Features are easy to explain). You have to remember to be careful in foreach
loops when you use __traits(parameters)
. You have to remember not because the case is truly an odd-one-out, but because … Well, I don’t know how to finish that sentence without being rude. A justified special case is: “If we used the simple rule, reasonable expectations would break. Therefore we have a complicated rule.” — Here, we have it backwards: “If we used the simple rule, we’d have a complicated compiler implementation. Therefore we break expectations.” The authors did not even have the mercy to make it an error so that it is guaranteed that programmers work around it. (The error message could claim it is amibiguous what you mean. It morally is not, but technically it is.)
The compiler does a bunch of stuff to properly lower return
, break
, continue
, and goto
statments in the foreach
body when given to opApply
. Why the authors of this feature decided not require it in this case is beyond me.
Workaround
Say you wanted to access the function's parameters in a foreach
loop. Say you are in a meta-programming context where you don't know the type of the range.
void f(R, Ts...)(R range, int param, Ts args)
{
foreach (auto ref x; range)
{
// some amount of code
g!(Ts[1..$])(x, __traits(parameters)[1..$]);
}
}
This is the idiomatic way to write the call to g
, but it might not work correctly depending on the details of the iteration of range
. Worse, it might compile and silently do unexpected stuff. The always-correct way:
void f(R, Ts...)(R range, int param, Ts args)
{
alias relevantParams = __traits(parameters)[1..$]; // Why?
foreach (auto ref x; range)
{
// some amount of code
g!(Ts[1..$])(x, relevantParams);
}
}