On 12/27/11 20:18, so wrote:
> On Tue, 27 Dec 2011 21:02:49 +0200, Artur Skawina <[email protected]> wrote:
> 
>> On 12/27/11 19:29, Vladimir Panteleev wrote:
>>> On Tuesday, 27 December 2011 at 18:00:27 UTC, Artur Skawina wrote:
>>>> Is there a way to *force* CTFE? Without using an extra wrapper like this:
>>>>
>>>> auto f_impl(alias a)() { / * ... ctfeable ... */ }
>>>> auto f(alias a)() { enum ctfed = f_impl!a; return ctfed; }
>>>>
>>>> A "@compile" (or "@ctfe" etc) function attribute would eliminate the need 
>>>> for the wrapper...
>>
>> Umm, the point was to *avoid* the extra useless wrapper. The only reason for 
>> its existence (and any kind of assignment to a static/enum) is to tell the 
>> compiler to try CTFE. Without the compile time assignment, it won't try hard 
>> enough - which means that, for f_impl()s that are not simple enough, code 
>> will be silently emitted and evaluated at runtime. Even when the functions 
>> are supposed to only do *compiletime* checking. (This happens eg. when 
>> /ctfeable/ contains loops)
>>
>> Hence the "@compile" attribute suggestion, and list selection.
>>
>> (I actually tried something similar to Now(), but that only obfuscates the 
>> code even more, for no gain)
>>
>> artur
> 
> // lib.d
> 
> T fun(T);
> 
> unittest
> {
>   enum a = fun;
> }
> 
> Should do the trick. No change in client code. Yes @ctfe_forced might be nice 
> but not that we lack a solution.
> I also like this one because it doesn't pollute the function.
> 

Hmm, maybe i'll try an example.

Let's say you want to calculate the value for use as a mask to strip off some 
high bits of an int. You only have the number of allowed values, which /is 
supposed to/ be a power of two. This is in generic, library code, so you can 
not assume anything more.
Obviously the answer is "uint POW2MASK(uint n) {return n-1;}"
But if somebody calls this with a wrong argument it will silently fail 
horribly. Also it is simple enough to always be fully inlined. And can then be 
made to work on more than just an int. And will often be used with a predefined 
constant 'n'.
The "safe" solution for the "unknown constant case" might look like this:

auto POW2MASK(alias N)() {
   ulong n = N;
   enum MSB = 1UL<<(n.sizeof*8-1);
   n--;
   while ((n&MSB)==0)
      n = (n<<1) + 1;
   if (n==n.max)
      return cast(typeof(N))(N-1);
   assert(0, N.stringof ~ " is not a power of two.");  // Abort compilation.
}

which can be used to statically check constants, like this:

private enum HASHSIZE_check_ = POW2MASK!HASHSIZE;


But now that we have this function, we can also use it in place of the open 
coded "n-1" or the original POW2MASK() in other (template) functions to gain 
some extra safety for free, right? "return (result&POW2MASK!N)" etc.

Wrong. The compiler will actually emit the constant-checking code, instead of 
just using the right constant. It will do this silently, so, unless you inspect 
the generated code, you might never realize this.

The solution? This:

// Given a constant power-of-two value (with just one bit set) return
// a 'mask' that can be used to mask of any higher bits so that
// the result fits into [0..N) range.
// Checks (at compile time) that the integer is a usable power of two.
// NOTE:
// "N==0" _is_ allowed, as it can be useful;
// "N==1" is not very useful (mask is zero) but is allowed too. 
// 'N' should probably be unsigned, but this function does not enforce
// this and will return the same type as that of 'N'.
//
// The implementation has to be split into two functions to force
// the compiler to execute the checks at compile time and not emit
// any of the ctfe-only code.
private auto POW2MASK_CTFE(alias N)() {
   ulong n = N;
   enum MSB = 1UL<<(n.sizeof*8-1);
   n--;
   while ((n&MSB)==0)
      n = (n<<1) + 1;
   if (n==n.max)
      return cast(typeof(N))(N-1);
   assert(0, N.stringof ~ " is not a power of two.");  // Abort compilation.
}
auto POW2MASK(alias N)() {
   enum R = POW2MASK_CTFE!N;
   return R;
}

Using this version we now get 100% bit-for-bit identical executable to the 
"N-1" open coded one.
Zero runtime cost, but the build will immediately fail if someone tries to use 
a non-power-of-2 value. (This has already caught at least one off-by-one bug 
here, btw)

This is just an example, that check could be done in n different ways, some of 
which would probably be completely optimized out (eg switch statement). CTFE 
can be very useful for things like checking constant requirements (especially 
when you have several /dependent/ constants) in templates, but if every time 
it's used to gain some extra safety you have to obfuscate the code, then it 
becomes much less attractive. 

The wrapper is there *only* to force CTFE, and *has* to be there for every 
single function that relies on ctfe happening, and probably would be a good 
idea for every function that is expected to evaluate at compile time. Omitting 
it could mean that even if things are ok now (ie no ctfe required), later when 
someone changes something (like adds another check after finding a bug) the 
result could be an unexpected app slowdown, with absolutely no warning. If you 
think that that someone should realize this -- well, even then, he/she has a 
choice of adding the check, then renaming the function and introducing a 
wrapper, or deciding this is not worth the effort... I know what i would do... 
;)

"auto @ctfe POW2MASK(alias N)() {...}" would be much more straightforward than 
the idiom above, wouldn't it? [1]

artur

[1] and i don't even like "@ctfe", but "@compile_time" would be too verbose...

Reply via email to