Thread overview
Best Way to Pass Template Typed Alias Parameters for Functions?
Dec 23, 2018
Vijay Nayar
Dec 23, 2018
Alex
Dec 23, 2018
Vijay Nayar
Dec 23, 2018
Alex
Dec 23, 2018
Vijay Nayar
Dec 23, 2018
Alex
Dec 23, 2018
Vijay Nayar
December 23, 2018
I have a few cases where I would like to pass in a function as a value to a template, but I want to ensure that the function takes certain kinds of parameters, is a const function, or matches any other set of conditions.

What is the best way to do this? Just to avoid any potential confusion, I've included a few examples below. I'm using explicit functions rather than lambdas just avoid any possibility of types being incorrectly inferred.

size_t myHashFunction(int a) { return cast(size_t) a; }

void main() {
    class A(KeyT, HashF) { }
    auto a = new A!(int, myHashFunction);
    // ^ onlineapp.d: Error: template instance `A!(int, myHashFunction)`
    // does not match template declaration `A(KeyT, HashF)`

    // Alias parameter: https://dlang.org/spec/template.html#aliasparameters
    class B(KeyT, alias HashF) { }
    auto b = new B!(int, myHashFunction);
    // ^ Works, but we cannot enforce the kind of function passed in.

    // Typed alias parameter: https://dlang.org/spec/template.html#typed_alias_op
    class C(KeyT, alias size_t function(KeyT) HashF) { }
    auto c = new C!(int, myHashFunction);
    // ^ onlineapp.d: Error: template instance `C!(int, myHashFunction)`
    // does not match template declaration `C(KeyT, alias size_t function(KeyT) HashF)`

    // Specialization: https://dlang.org/spec/template.html#alias_parameter_specialization
    class D(KeyT, alias HashF : size_t function(KeyT)) { }
    auto d = new D!(int, myHashFunction
    // ^ onlineapp.d: Error: template instance `D!(int, myHashFunction)`
    // does not match template declaration `D(KeyT, alias HashF : size_t function(KeyT))`
}

Looking at some of the code in std.algorithm, it seem that static asserts are sometimes used for this purpose. Does anyone have a solution to this problem of instantiating template classes whose parameters are functions?  I have used std.function "unaryFun" before, but this has the problem of not being able to specify function attributes, like const.
December 23, 2018
On Sunday, 23 December 2018 at 17:13:49 UTC, Vijay Nayar wrote:
> I have a few cases where I would like to pass in a function as a value to a template, but I want to ensure that the function takes certain kinds of parameters, is a const function, or matches any other set of conditions.
>
> What is the best way to do this? Just to avoid any potential confusion, I've included a few examples below. I'm using explicit functions rather than lambdas just avoid any possibility of types being incorrectly inferred.
>
> size_t myHashFunction(int a) { return cast(size_t) a; }
>
> void main() {
>     class A(KeyT, HashF) { }
>     auto a = new A!(int, myHashFunction);
>     // ^ onlineapp.d: Error: template instance `A!(int, myHashFunction)`
>     // does not match template declaration `A(KeyT, HashF)`
>
>     // Alias parameter: https://dlang.org/spec/template.html#aliasparameters
>     class B(KeyT, alias HashF) { }
>     auto b = new B!(int, myHashFunction);
>     // ^ Works, but we cannot enforce the kind of function passed in.
>
>     // Typed alias parameter: https://dlang.org/spec/template.html#typed_alias_op
>     class C(KeyT, alias size_t function(KeyT) HashF) { }
>     auto c = new C!(int, myHashFunction);
>     // ^ onlineapp.d: Error: template instance `C!(int, myHashFunction)`
>     // does not match template declaration `C(KeyT, alias size_t function(KeyT) HashF)`
>
>     // Specialization: https://dlang.org/spec/template.html#alias_parameter_specialization
>     class D(KeyT, alias HashF : size_t function(KeyT)) { }
>     auto d = new D!(int, myHashFunction
>     // ^ onlineapp.d: Error: template instance `D!(int, myHashFunction)`
>     // does not match template declaration `D(KeyT, alias HashF : size_t function(KeyT))`
> }
>
> Looking at some of the code in std.algorithm, it seem that static asserts are sometimes used for this purpose. Does anyone have a solution to this problem of instantiating template classes whose parameters are functions?  I have used std.function "unaryFun" before, but this has the problem of not being able to specify function attributes, like const.

I assume you are looking for constraints...
https://dlang.org/concepts.html

Then, case B is the way to go, see
https://run.dlang.io/is/jWU3tr

Case D also looks promising. Not sure, how to formulate the specialization right now...

There are a lot of traits you can use for the constraints.
https://dlang.org/phobos/std_traits.html
and
https://dlang.org/spec/traits.html
e.g., for constness there is getFunctionAttributes, which could be useful.
December 23, 2018
On Sunday, 23 December 2018 at 18:04:32 UTC, Alex wrote:
> On Sunday, 23 December 2018 at 17:13:49 UTC, Vijay Nayar wrote:
>> I have a few cases where I would like to pass in a function as a value to a template, but I want to ensure that the function takes certain kinds of parameters, is a const function, or matches any other set of conditions.
>
> I assume you are looking for constraints...
> https://dlang.org/concepts.html
>
> Then, case B is the way to go, see
> https://run.dlang.io/is/jWU3tr
>
> Case D also looks promising. Not sure, how to formulate the specialization right now...
>
> There are a lot of traits you can use for the constraints.
> https://dlang.org/phobos/std_traits.html
> and
> https://dlang.org/spec/traits.html
> e.g., for constness there is getFunctionAttributes, which could be useful.

I've been playing with the idea of specifying the constraints or using "static assert" in the constructor. They are good options, but there's a few cases where they fall a bit short.  For example, imagine you have a container class that needs a comparator function to be able to compare the items in the container.  While you can use std.traits to assure the right kind of function is passed in, that information does not make its way into the type system.

For example, if you have a const function in your container like "T find() const", and this function needs to use that comparator, then you're out of luck because the compiler doesn't know that the comparator function is const and will not modify the objects being compared.

Last time I ran into this problem, my solution was simply to give up on const. But now I'm running into it again and trying to think through it again before giving up again.
December 23, 2018
On Sunday, 23 December 2018 at 18:13:25 UTC, Vijay Nayar wrote:
> I've been playing with the idea of specifying the constraints or using "static assert" in the constructor. They are good options, but there's a few cases where they fall a bit short.  For example, imagine you have a container class that needs a comparator function to be able to compare the items in the container.  While you can use std.traits to assure the right kind of function is passed in, that information does not make its way into the type system.
>
> For example, if you have a const function in your container like "T find() const", and this function needs to use that comparator, then you're out of luck because the compiler doesn't know that the comparator function is const and will not modify the objects being compared.
>
> Last time I ran into this problem, my solution was simply to give up on const. But now I'm running into it again and trying to think through it again before giving up again.

Hm... still not sure... ;)
This would compile and run:

´´´
import std.experimental.all;

size_t myHashFunction(int a) { return cast(size_t) a; }

void main()
{
	auto b = new B!(int, myHashFunction);
	b.arr = 42.iota.array;
	assert(b.find == 1);
}

class B(T, alias HashF)
{
	T[] arr;

	T find() const
	{
		foreach(el; arr)
		{
			if(HashF(el))
			{
				return el;
			}
		}
		assert(0);
	}
}
´´´
December 23, 2018
On Sunday, 23 December 2018 at 18:31:24 UTC, Alex wrote:
> On Sunday, 23 December 2018 at 18:13:25 UTC, Vijay Nayar wrote:
>> For example, if you have a const function in your container like "T find() const", and this function needs to use that comparator, then you're out of luck because the compiler doesn't know that the comparator function is const and will not modify the objects being compared.
>>
>> Last time I ran into this problem, my solution was simply to give up on const. But now I'm running into it again and trying to think through it again before giving up again.
>
> Hm... still not sure... ;)
> This would compile and run:
>
> ´´´
> import std.experimental.all;
>
> size_t myHashFunction(int a) { return cast(size_t) a; }
>
> void main()
> {
> 	auto b = new B!(int, myHashFunction);
> 	b.arr = 42.iota.array;
> 	assert(b.find == 1);
> }
>
> class B(T, alias HashF)
> {
> 	T[] arr;
>
> 	T find() const
> 	{
> 		foreach(el; arr)
> 		{
> 			if(HashF(el))
> 			{
> 				return el;
> 			}
> 		}
> 		assert(0);
> 	}
> }
> ´´´

You're right, it does compile. I'm a bit surprised. I wonder if this is a relatively recent improvement in the language, because last time I ran into this I had no such luck. But after seeing that your example did work, I figured one could try to get the best of both worlds by using a strongly-typed wrapper function in one's class.  So far it seems to work:

import std.traits;

class A(KeyT, alias HashF) {
  // Strongly-typed wrapper around template value parameter 'HashF'.
  static size_t hash(in KeyT key) {
    return HashF(key);
  }
  static this() {
    static assert(isCallable!HashF, "Hash function is not callable!");
    static assert(Parameters!(HashF).length == 1, "Hash function must take 1 argument.");
    static assert(is(Parameters!(HashF)[0] : const(KeyT)),
        "Hash parameter must be const.");
    static assert(is(typeof(HashF(KeyT.init)) : size_t),
        "Hash function must return size_t type.");
  }

  KeyT data;
  size_t getHash() const {
    return hash(data);
  }
}

void main() {
    auto a = new A!(int, (int a) => cast(size_t) a);
    a.data = 5;
    a.getHash();
}
December 23, 2018
On Sunday, 23 December 2018 at 18:53:15 UTC, Vijay Nayar wrote:
> You're right, it does compile. I'm a bit surprised. I wonder if this is a relatively recent improvement in the language, because last time I ran into this I had no such luck. But after seeing that your example did work, I figured one could try to get the best of both worlds by using a strongly-typed wrapper function in one's class.  So far it seems to work:
>
> import std.traits;
>
> class A(KeyT, alias HashF) {
>   // Strongly-typed wrapper around template value parameter 'HashF'.
>   static size_t hash(in KeyT key) {
>     return HashF(key);
>   }
>   static this() {
>     static assert(isCallable!HashF, "Hash function is not callable!");
>     static assert(Parameters!(HashF).length == 1, "Hash function must take 1 argument.");
>     static assert(is(Parameters!(HashF)[0] : const(KeyT)),
>         "Hash parameter must be const.");
>     static assert(is(typeof(HashF(KeyT.init)) : size_t),
>         "Hash function must return size_t type.");
>   }
>
>   KeyT data;
>   size_t getHash() const {
>     return hash(data);
>   }
> }
>
> void main() {
>     auto a = new A!(int, (int a) => cast(size_t) a);
>     a.data = 5;
>     a.getHash();
> }

I'm not sure, whether you need the static this() part at all, as all of the asserts the compiler should do even when they are absent...

by isCallable you restrict the HashF to not use IFTI
by calling HashF(key) you ensure implicitely, that Parameters!(HashF).length == 1
by having hash(in KeyT key) defined with an "in" you ensured, that HashF does not mutate the argument
and by defining size_t getHash() you ensured the return type of HashF...

December 23, 2018
On Sunday, 23 December 2018 at 19:10:06 UTC, Alex wrote:
> On Sunday, 23 December 2018 at 18:53:15 UTC, Vijay Nayar wrote:
>> You're right, it does compile. I'm a bit surprised. I wonder if this is a relatively recent improvement in the language, because last time I ran into this I had no such luck. But after seeing that your example did work, I figured one could try to get the best of both worlds by using a strongly-typed wrapper function in one's class.  So far it seems to work:
>>
>> import std.traits;
>>
>> class A(KeyT, alias HashF) {
>>   // Strongly-typed wrapper around template value parameter 'HashF'.
>>   static size_t hash(in KeyT key) {
>>     return HashF(key);
>>   }
>>   static this() {
>>     static assert(isCallable!HashF, "Hash function is not callable!");
>>     static assert(Parameters!(HashF).length == 1, "Hash function must take 1 argument.");
>>     static assert(is(Parameters!(HashF)[0] : const(KeyT)),
>>         "Hash parameter must be const.");
>>     static assert(is(typeof(HashF(KeyT.init)) : size_t),
>>         "Hash function must return size_t type.");
>>   }
>>
>>   KeyT data;
>>   size_t getHash() const {
>>     return hash(data);
>>   }
>> }
>>
>> void main() {
>>     auto a = new A!(int, (int a) => cast(size_t) a);
>>     a.data = 5;
>>     a.getHash();
>> }
>
> I'm not sure, whether you need the static this() part at all, as all of the asserts the compiler should do even when they are absent...
>
> by isCallable you restrict the HashF to not use IFTI
> by calling HashF(key) you ensure implicitely, that Parameters!(HashF).length == 1
> by having hash(in KeyT key) defined with an "in" you ensured, that HashF does not mutate the argument
> and by defining size_t getHash() you ensured the return type of HashF...

You are correct again! Playing around with using classes and functions returning the wrong type or not having a const argument, it seems that the compiler will assure that all the conditions needed by the wrapper function are satisfied and give a clear error.

I don't know when this happened, but this definitely makes the language a lot easier to use for these circumstances, and all the std.traits stuff in "static this()" can be thrown out.