Thread overview
Static Foreach + Marking "compile time" variables
Mar 28, 2018
Chris Katko
Mar 28, 2018
Simen Kjærås
Mar 28, 2018
Chris Katko
Mar 28, 2018
Chris Katko
Mar 28, 2018
Chris Katko
Mar 28, 2018
Chris Katko
Mar 28, 2018
Chris Katko
Mar 28, 2018
Simen Kjærås
Mar 29, 2018
Chris Katko
March 28, 2018
I have a static foreach that goes through the parameter list and if it sees a class like "rotate", ideally, I want it to mark a boolean "has_rotate=true"

Then simply later on, once I've parsed the list, I pick an output path:

    static if(has_rotate && has_position && has_scale)
    {
    //call C function al_draw_rotated_scaled_bitmap(...);
    }

So I pass a bunch of "options" and then based on those options, it picks the right function to call instead of me manually writing draw_fixed_centered_rotated_scaled_colored_compressed_bitmap() manually.

The problem is, I can foreach through those parameters all just fine... but I can't "set" a marker.

The error message makes perfect sense in retrospect:

     variable has_position cannot be read at compile time

has_position isn't a static/compile-time variable. The question is... do they exist in D? Because otherwise, I'll be going from a really clean, elegant solution that simply passes through each parameter, and then reacts to the combined result, and end up with something that has to "step toward" the result, or perhaps, test every possible permutation individually--so as to not need variables.
March 28, 2018
On Wednesday, 28 March 2018 at 07:45:59 UTC, Chris Katko wrote:
> I have a static foreach that goes through the parameter list and if it sees a class like "rotate", ideally, I want it to mark a boolean "has_rotate=true"
>
> Then simply later on, once I've parsed the list, I pick an output path:
>
>     static if(has_rotate && has_position && has_scale)
>     {
>     //call C function al_draw_rotated_scaled_bitmap(...);
>     }
>
> So I pass a bunch of "options" and then based on those options, it picks the right function to call instead of me manually writing draw_fixed_centered_rotated_scaled_colored_compressed_bitmap() manually.
>
> The problem is, I can foreach through those parameters all just fine... but I can't "set" a marker.
>
> The error message makes perfect sense in retrospect:
>
>      variable has_position cannot be read at compile time
>
> has_position isn't a static/compile-time variable. The question is... do they exist in D? Because otherwise, I'll be going from a really clean, elegant solution that simply passes through each parameter, and then reacts to the combined result, and end up with something that has to "step toward" the result, or perhaps, test every possible permutation individually--so as to not need variables.

D does not have compile-time variables. The way you'd generally do what you describe is with the templates in std.meta. Instead of something like this:

    bool hasRotate= false;
    static foreach (e; MyTuple) {
        hasRotate |= is(e == Rotate);
    }
    static if (hasRotate) { // Fails, since b is a runtime variable.
        // Stuff
    }

You'd be using a more functional style:

    template isa(T) {
        enum isa(U) = is(U == T);
    }

    enum hasRotate = anySatisfy!(isa!Rotate, MyTuple);
    static if (hasRotate) {
        // Stuff
    }

--
  Simen
March 28, 2018
On Wednesday, 28 March 2018 at 08:05:55 UTC, Simen Kjærås wrote:
> On Wednesday, 28 March 2018 at 07:45:59 UTC, Chris Katko wrote:
>> I have a static foreach that goes through the parameter list and if it sees a class like "rotate", ideally, I want it to mark a boolean "has_rotate=true"
>>
>> Then simply later on, once I've parsed the list, I pick an output path:
>>
>>     static if(has_rotate && has_position && has_scale)
>>     {
>>     //call C function al_draw_rotated_scaled_bitmap(...);
>>     }
>>
>> So I pass a bunch of "options" and then based on those options, it picks the right function to call instead of me manually writing draw_fixed_centered_rotated_scaled_colored_compressed_bitmap() manually.
>>
>> The problem is, I can foreach through those parameters all just fine... but I can't "set" a marker.
>>
>> The error message makes perfect sense in retrospect:
>>
>>      variable has_position cannot be read at compile time
>>
>> has_position isn't a static/compile-time variable. The question is... do they exist in D? Because otherwise, I'll be going from a really clean, elegant solution that simply passes through each parameter, and then reacts to the combined result, and end up with something that has to "step toward" the result, or perhaps, test every possible permutation individually--so as to not need variables.
>
> D does not have compile-time variables. The way you'd generally do what you describe is with the templates in std.meta. Instead of something like this:
>
>     bool hasRotate= false;
>     static foreach (e; MyTuple) {
>         hasRotate |= is(e == Rotate);
>     }
>     static if (hasRotate) { // Fails, since b is a runtime variable.
>         // Stuff
>     }
>
> You'd be using a more functional style:
>
>     template isa(T) {
>         enum isa(U) = is(U == T);
>     }
>
>     enum hasRotate = anySatisfy!(isa!Rotate, MyTuple);
>     static if (hasRotate) {
>         // Stuff
>     }
>
> --
>   Simen

Thank you for the reply! But I'm unable to get that code to compile. For example:

/usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_1) does not match template declaration isIntegral(T)
/usr/local/bin/../import/std/meta.d(888): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isIntegral, _param_1) error instantiating
extra.d(264):        instantiated from here: anySatisfy!(isIntegral, _param_1, _param_2, _param_3)
extra.d(385):        instantiated from here: funct2!(int, int, int)
/usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_2) does not match template declaration isIntegral(T)
/usr/local/bin/../import/std/meta.d(888): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isIntegral, _param_2) error instantiating
/usr/local/bin/../import/std/meta.d(889):        instantiated from here: anySatisfy!(isIntegral, _param_2, _param_3)
extra.d(264):        instantiated from here: anySatisfy!(isIntegral, _param_1, _param_2, _param_3)
extra.d(385):        instantiated from here: funct2!(int, int, int)
/usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_3) does not match template declaration isIntegral(T)
/usr/local/bin/../import/std/meta.d(889): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isIntegral, _param_3) error instantiating
/usr/local/bin/../import/std/meta.d(889):        instantiated from here: anySatisfy!(isIntegral, _param_2, _param_3)
extra.d(264):        instantiated from here: anySatisfy!(isIntegral, _param_1, _param_2, _param_3)
extra.d(385):        instantiated from here: funct2!(int, int, int)


Perhaps I should post more of my code because there may be subtle differences that are important:

struct pos{float x, y;}
struct scale{float f;}
struct rotate{ float a; }

void test_inst()
	{
	funct2(tile_bmp, pos(100,100), scale(2), rotate(0.0f));
        //this worked with the foreach version
        // in the case that it compiled and read the arguments
        }

Then I use your version:

template isa(T) {
        enum isa(U) = is(U == T); //is(typeof(U) == T)  doesn't work either.
     }

void funct2(A...)(ALLEGRO_BITMAP *bitmap_bmp, A a)
	{
	enum hasRotate = anySatisfy!( isa(pos), a);  //if of type "pos"
    static if (hasRotate)
		{
                // Stuff
		}
	}

March 28, 2018
On Wednesday, 28 March 2018 at 15:46:42 UTC, Chris Katko wrote:
> On Wednesday, 28 March 2018 at 08:05:55 UTC, Simen Kjærås wrote:
>> On Wednesday, 28 March 2018 at 07:45:59 UTC, Chris Katko wrote:
>>> I have a static foreach that goes through the parameter list and if it sees a class like "rotate", ideally, I want it to mark a boolean "has_rotate=true"
>>>
>>> Then simply later on, once I've parsed the list, I pick an output path:
>>>
>>>     static if(has_rotate && has_position && has_scale)
>>>     {
>>>     //call C function al_draw_rotated_scaled_bitmap(...);
>>>     }
>>>
>>> So I pass a bunch of "options" and then based on those options, it picks the right function to call instead of me manually writing draw_fixed_centered_rotated_scaled_colored_compressed_bitmap() manually.
>>>
>>> The problem is, I can foreach through those parameters all just fine... but I can't "set" a marker.
>>>
>>> The error message makes perfect sense in retrospect:
>>>
>>>      variable has_position cannot be read at compile time
>>>
>>> has_position isn't a static/compile-time variable. The question is... do they exist in D? Because otherwise, I'll be going from a really clean, elegant solution that simply passes through each parameter, and then reacts to the combined result, and end up with something that has to "step toward" the result, or perhaps, test every possible permutation individually--so as to not need variables.
>>
>> D does not have compile-time variables. The way you'd generally do what you describe is with the templates in std.meta. Instead of something like this:
>>
>>     bool hasRotate= false;
>>     static foreach (e; MyTuple) {
>>         hasRotate |= is(e == Rotate);
>>     }
>>     static if (hasRotate) { // Fails, since b is a runtime variable.
>>         // Stuff
>>     }
>>
>> You'd be using a more functional style:
>>
>>     template isa(T) {
>>         enum isa(U) = is(U == T);
>>     }
>>
>>     enum hasRotate = anySatisfy!(isa!Rotate, MyTuple);
>>     static if (hasRotate) {
>>         // Stuff
>>     }
>>
>> --
>>   Simen
>
> Thank you for the reply! But I'm unable to get that code to compile. For example:
>
> /usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_1) does not match template declaration isIntegral(T)
> /usr/local/bin/../import/std/meta.d(888): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isIntegral, _param_1) error instantiating
> extra.d(264):        instantiated from here: anySatisfy!(isIntegral, _param_1, _param_2, _param_3)
> extra.d(385):        instantiated from here: funct2!(int, int, int)
> /usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_2) does not match template declaration isIntegral(T)
> /usr/local/bin/../import/std/meta.d(888): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isIntegral, _param_2) error instantiating
> /usr/local/bin/../import/std/meta.d(889):        instantiated from here: anySatisfy!(isIntegral, _param_2, _param_3)
> extra.d(264):        instantiated from here: anySatisfy!(isIntegral, _param_1, _param_2, _param_3)
> extra.d(385):        instantiated from here: funct2!(int, int, int)
> /usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_3) does not match template declaration isIntegral(T)
> /usr/local/bin/../import/std/meta.d(889): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isIntegral, _param_3) error instantiating
> /usr/local/bin/../import/std/meta.d(889):        instantiated from here: anySatisfy!(isIntegral, _param_2, _param_3)
> extra.d(264):        instantiated from here: anySatisfy!(isIntegral, _param_1, _param_2, _param_3)
> extra.d(385):        instantiated from here: funct2!(int, int, int)
>
>
> Perhaps I should post more of my code because there may be subtle differences that are important:
>
> struct pos{float x, y;}
> struct scale{float f;}
> struct rotate{ float a; }
>
> void test_inst()
> 	{
> 	funct2(tile_bmp, pos(100,100), scale(2), rotate(0.0f));
>         //this worked with the foreach version
>         // in the case that it compiled and read the arguments
>         }
>
> Then I use your version:
>
> template isa(T) {
>         enum isa(U) = is(U == T); //is(typeof(U) == T)  doesn't work either.
>      }
>
> void funct2(A...)(ALLEGRO_BITMAP *bitmap_bmp, A a)
> 	{
> 	enum hasRotate = anySatisfy!( isa(pos), a);  //if of type "pos"
>     static if (hasRotate)
> 		{
>                 // Stuff
> 		}
> 	}

Whoops! Wrong error message. That's if I replace isa(pos) with IsIntegral.

If I have isa(pos). It's almost identical:

/usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_1) does not match template declaration isa(U)
/usr/local/bin/../import/std/meta.d(888): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isa, _param_1) error instantiating
extra.d(263):        instantiated from here: anySatisfy!(isa, _param_1, _param_2, _param_3)
extra.d(385):        instantiated from here: funct2!(int, int, int)
/usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_2) does not match template declaration isa(U)
/usr/local/bin/../import/std/meta.d(888): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isa, _param_2) error instantiating
/usr/local/bin/../import/std/meta.d(889):        instantiated from here: anySatisfy!(isa, _param_2, _param_3)
extra.d(263):        instantiated from here: anySatisfy!(isa, _param_1, _param_2, _param_3)
extra.d(385):        instantiated from here: funct2!(int, int, int)
/usr/local/bin/../import/std/meta.d(883): Error: template instance F!(_param_3) does not match template declaration isa(U)
/usr/local/bin/../import/std/meta.d(889): Error: template instance extra.funct2!(int, int, int).funct2.anySatisfy!(isa, _param_3) error instantiating
/usr/local/bin/../import/std/meta.d(889):        instantiated from here: anySatisfy!(isa, _param_2, _param_3)
extra.d(263):        instantiated from here: anySatisfy!(isa, _param_1, _param_2, _param_3)
extra.d(385):        instantiated from here: funct2!(int, int, int)

March 28, 2018
On Wednesday, 28 March 2018 at 15:49:39 UTC, Chris Katko wrote:
> On Wednesday, 28 March 2018 at 15:46:42 UTC, Chris Katko wrote:
>> [...]
>
> Whoops! Wrong error message. That's if I replace isa(pos) with IsIntegral.
>
> [...]

Okay, the key appears to be here:

funct2(123); //a function call

void funct2(A...)(A a)
	{
	enum hasRotate = isIntegral!(a[0]); // NOPE!
        }

//E: template instance isIntegral!(_param_0) does not match template declaration isIntegral(T)

But if I do:

funct2!(123); //note !

I get

//E: tuple A is used as a type
March 28, 2018
On Wednesday, 28 March 2018 at 17:08:39 UTC, Chris Katko wrote:
> On Wednesday, 28 March 2018 at 15:49:39 UTC, Chris Katko wrote:
>> On Wednesday, 28 March 2018 at 15:46:42 UTC, Chris Katko wrote:
>>> [...]
>>
>> Whoops! Wrong error message. That's if I replace isa(pos) with IsIntegral.
>>
>> [...]
>
> Okay, the key appears to be here:
>
> funct2(123); //a function call
>
> void funct2(A...)(A a)
> 	{
> 	enum hasRotate = isIntegral!(a[0]); // NOPE!
>         }
>
> //E: template instance isIntegral!(_param_0) does not match template declaration isIntegral(T)
>
> But if I do:
>
> funct2!(123); //note !
>
> I get
>
> //E: tuple A is used as a type


GOT IT!!

I needed typeof on the right side.

template isa(T){enum isa(U) = is(U == T); }

funct2(g.tile_bmp, pos(100,100), scale(2), rotate(0.0f));

void funct2(A...)(ALLEGRO_BITMAP *bit, A a)
	{
	enum hasPosition	= anySatisfy!(isa!(pos), typeof(a));
        }

Works!
March 28, 2018
On 3/28/18 11:46 AM, Chris Katko wrote:

>      enum hasRotate = anySatisfy!( isa(pos), a);  //if of type "pos"

anySatisfy!(isa!pos, a)

anySatisfy takes a template alias (in this case, an instantiation of isa with a specific type), and then applies the template to all the elements of the alias sequence, returning true if any statisfy.

-Steve
March 28, 2018
On Wednesday, 28 March 2018 at 17:42:45 UTC, Steven Schveighoffer wrote:
> On 3/28/18 11:46 AM, Chris Katko wrote:
>
>>      enum hasRotate = anySatisfy!( isa(pos), a);  //if of type "pos"
>
> anySatisfy!(isa!pos, a)
>
> anySatisfy takes a template alias (in this case, an instantiation of isa with a specific type), and then applies the template to all the elements of the alias sequence, returning true if any statisfy.
>
> -Steve

Yeah, that was one of my errors during my 4 AM binge. But I forgot/missed to update that when I posted.

So far, everything is working with some dummy calls, and now I just have to start doing the annoying work of writing the code.

It's just... I wish it was a little more elegant. Right now, I'm stuck with like tons of static if cases. [see code at end of post] If I add "centered position" vs "position" now, I have to figure out how to add that into a single compound statement to fit into a unique static if. As opposed to simply, "if centered pos, take position, and just change it by subtracting width and height."  In that case, I can get away with leaving them run-time statements, but more complex features not-so-much. The point is to use compile-time/static/templates to build the correct statement. Because at the core of what I'm doing... it IS all statically / compile-time knowable information.

And having TONS of static ifs means there's TONS of room for human error as its maintained.

If I wrote a Python/whatever script that pre-processed my D code from the AST, I'd have no theoretical reason to prevent me from doing this (though, clearly with much added frustration of implementation and debugging!).

I'm starting to wonder if a mixin will somehow help... hmm. I'm going to keep thinking about the problem.

Some code will probably help explain what I'm talking about:

funct2(g.tile_bmp, pos(100,100), scale(2), rotate(0.0f), center());

template isa(T){enum isa(U) = is(U == T); }

void funct2(A...)(ALLEGRO_BITMAP *bit, A a)
	{
	enum hasPosition = anySatisfy!(isa!(pos), typeof(a));
	enum hasCenteredPosition = anySatisfy!(isa!(center), typeof(a));

	enum hasRotate 		= anySatisfy!(isa!(rotate), typeof(a));
	enum hasScale 		= anySatisfy!(isa!(scale), typeof(a));

    static if (hasPosition && hasCenteredPosition)
		{
		float temp_x=a[staticIndexOf!(pos, typeof(a))].x - bit.w/2;
		float temp_y=a[staticIndexOf!(pos, typeof(a))].y - bit.h/2;
		}else{
		float temp_x=a[staticIndexOf!(pos, typeof(a))].x;
		float temp_y=a[staticIndexOf!(pos, typeof(a))].y;
		}

    static if (!hasPosition)
		{
		assert(0, "Need to pass a position!");
		}

    static if (hasPosition && !hasRotate && !hasScale)
		{
        // Stuff
		pragma(msg, "Class 1");
		writeln("x ", a[staticIndexOf!(pos, typeof(a))].x);
		writeln("y ", a[staticIndexOf!(pos, typeof(a))].y);
		
		al_draw_bitmap(bit,
			temp_x,
			temp_y,
			0);
		}

    static if (hasPosition && hasRotate && !hasScale)
		{
        // Stuff
		pragma(msg, "Class 2");
		writeln("x ", a[staticIndexOf!(pos, typeof(a))].x);
		writeln("y ", a[staticIndexOf!(pos, typeof(a))].y);
		
		al_draw_rotated_bitmap(bit,
			bit.w/2,
			bit.h/2,
			temp_x,
			temp_y,
			a[staticIndexOf!(rotate, typeof(a))].a,
			0);
				
		}


    static if (hasPosition && !hasRotate && hasScale)
		{
		pragma(msg, "Class 3");
		}

    static if (hasPosition && hasRotate && hasScale)
		{
		pragma(msg, "Class 4");
		}




	}
March 28, 2018
On Wednesday, 28 March 2018 at 23:02:53 UTC, Chris Katko wrote:

There's many things that can be done to make the code easier to follow. These lines:
>     static if (!hasPosition)
> 		{
> 		assert(0, "Need to pass a position!");
> 		}

Can be replaced with this:

    static assert(hasPosition, "Need to pass a position");

That will show the error message at compile-time, greatly improving the experience of using the code. You could also us it as a template constraint:

    void funct2(A...)(ALLEGRO_BITMAP *bit, A a)
    if (anySatisfy!(isa!Position, A))
    {

There are benefits and drawbacks to using template constraints vs. static asserts, so try both and see which fits your code.


Since hasPosition needs to be true for anything else to work, we can just assert it once, then assume it's true, turning these:

>     static if (hasPosition && hasCenteredPosition)
>     static if (hasPosition && hasRotate && !hasScale)

into these:

    static if (hasCenteredPosition)
    static if (hasRotate && !hasScale)


Another possible simplification in your code would be to use staticIndexOf instead of anySatisfy:

    enum position = staticIndexOf!(pos, A);
    static assert(position > -1, "Must pass a position");
    enum hasCenteredPosition = anySatisfy!(isa!center, A);

    static if (hasCenteredPosition) {
        float temp_x = a[position].x - bit.w/2;
        float temp_y = a[position].y - bit.h/2;
    } else {
        float temp_x = a[position].x;
        float temp_y = a[position].y;
    }

In fact, given the style in which you write your code, I would suggest these helper templates:

// Returns the first element of Args that is of type T,
// or an empty AliasSeq if no such element exists.
template find(T, Args...) {
    enum idx = staticIndexOf!(T, typeof(Args));
    static if (idx > -1) alias find = Args[idx];
    else                 alias find = AliasSeq!();
}

// Checks if find!() found anything.
enum found(T...) = T.length == 1;


Using those, you would write this code:

void funct2(A...)(ALLEGRO_BITMAP* bit, A a)
if (anySatisfy!(isa!pos, A)
{
    enum centered  = anySatisfy!(isa!center, A)

    alias position = find!(pos, a);
    alias rotation = find!(rotate, a);
    alias scaling  = find!(scale, a);

    static if (centered) {
        // No need to use array lookup - find!() did that for us.
        // That also means we can more easily modify position directly,
        // instead of using temporaries.
        position.x -= bit.w/2;
        position.y -= bit.h/2;
    }

    static if (!found!rotation && !found!scaling) {
        // Since rotation and scaling are empty AliasSeqs here,
        // attempting to use them will cause immediate compile errors.
        al_draw_bitmap(bit, position.x, position.y, 0);
    } else static if (found!rotation && !found!scaling) {
        al_draw_rotated_bitmap(bit,
            bit.w/2,    bit.h/2,
            position.x, position.y,
            rotation.a,
            0);
    } else static if (found!rotation && found!scaling) {
        // Handle this case.
    } else static if (!found!rotation && found!scaling) {
        // Handle this case.
    }
}

--
  Simen
March 29, 2018
On Wednesday, 28 March 2018 at 23:42:26 UTC, Simen Kjærås wrote:
> On Wednesday, 28 March 2018 at 23:02:53 UTC, Chris Katko wrote:
>
> There's many things that can be done to make the code easier to follow. These lines:
>> [...]
>
> [...]

WOW. Thank you. That's the kind of tricks for (or more properly: knowledge of) templates that I can use. That's much more clean looking and maintainable. Feel free to recommend more ideas/tricks!

I'm still definitely working on improving (and developing) that template/function so yeah, I left those duplicate static if's in at the time (I called it "quits" after getting it to actually compile, so there's still scribble in there.)