Questions on the new __traits(parameters)

Quirin Schroll quirin.schroll at mgm-tp.com
Thu Mar 10 10:18:09 UTC 2022


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.
```D
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:
```D
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);
     }
}
```


More information about the Digitalmars-d mailing list