Thread overview
Templated delegate as template argument for structs
Feb 20, 2021
Simon van Bernem
Feb 20, 2021
Simen Kjærås
Feb 20, 2021
Simon van Bernem
February 20, 2021
I have the following struct declaration:


struct Hash_Table(Key, Value, u32 delegate(ref Key) custom_hash_function = null){

...

}


When I try to instance the Type like this:


Hash_Table!(Component*, Component_Tick_Info, (c) => hash32(c.handle.bitfield)) my_hash_table;


I get the following compile error


source\vbl.d(2338): Error: undefined identifier `Key`
source\vessel.d(840):        while looking for match for `__lambda4!Key`
source\vessel.d(840):        while looking for match for `Hash_Table!(Component*, Component_Tick_Info, (c) => hash32(c.handle.bitfield))`


vbl.d(2338) is the line of the struct declaration.

I take it from the error that the problem is not actually the delegate that I am passing, but the fact that the delegate type has another template parameter as the argument type. Can template arguments in D not reference previous template arguments? Is there some way I can get around this?

February 20, 2021
On Saturday, 20 February 2021 at 09:16:46 UTC, Simon van Bernem wrote:
> I have the following struct declaration:
>
> struct Hash_Table(Key, Value, u32 delegate(ref Key) custom_hash_function = null)
[snip]
> I take it from the error that the problem is not actually the delegate that I am passing, but the fact that the delegate type has another template parameter as the argument type. Can template arguments in D not reference previous template arguments? Is there some way I can get around this?

The D way would be an alias parameter:

struct Hash_Table(Key, Value, alias custom_hash_function) {
    // ...
}

Probably also adding a constraint:

struct Hash_Table(Key, Value, alias custom_hash_function)
if (is_valid_hash_function!(Key, custom_hash_function))
{
    // ...
}

// Check if the hash function can be called with a Key
// as argument, and the result be assigned to a u32
enum is_valid_hash_function(Key, alias fn) = __traits(compiles, (Key k){ u32 u = fn(k); });


D lets you refer to other template parameters in the same template parameter list in three cases, as far as I know:

1) A type may be a subtype of an earlier parameter:

    class C {}
    struct S(TBase, TDerived : TBase) {}
    S!(Object, C) a;


2) A type parameter with a type specialization followed by template parameter list:

    struct Fn(U) {}
    struct S(T: Fn!Arg, Arg) {}
    S!(Fn!int) a;


3) Following a type parameter, a value parameter of that type may appear:

    struct S(T, T value) {}
    S!(int, 4) a;


The first two can also be combined:

    struct Fn(U) {}
    struct S(T1, T2: Fn!T3, T3 : T1) {}
    S!(int, Fn!int) a;


However, it seems 3) only works with that specific type, modulo storage classes. No Foo!T, no void delegate(T). At any rate, the alias parameter is the way it's generally done, and is more flexible, so just leave it at that.

--
  Simen
February 20, 2021
Thanks! The alias solution works and is good enough for me. Also thanks for providing the code to typecheck the alias, I would have never been able to come up with that myself.