Thread overview
Clash When Using Function as Template Value-Parameters?
May 26, 2018
Vijay Nayar
May 27, 2018
Vijay Nayar
May 27, 2018
Daniel Kozak
May 28, 2018
Vijay Nayar
May 29, 2018
Yuxuan Shui
May 29, 2018
Yuxuan Shui
May 29, 2018
Vijay Nayar
May 29, 2018
Yuxuan Shui
May 29, 2018
Vijay Nayar
May 30, 2018
Vijay Nayar
May 26, 2018
I've been experimenting with code that uses std.functional : binaryFun and unaryFun, but I have found that using these methods makes it impossible to add function attributes like @safe, @nogc, pure, and nothrow, because no guarantee can be made about the functions created via a stream.  For example, if you expect a comparator function like "a == b", someone can pass in "a.data--" instead.

That being said, I started trying out using strongly typed and attributed template parameters instead, relying on lambdas to keep the syntax for the user short. But when I tried this, I found that the very existence of templates with different parameter values causes a collision during compilation.

The following code snippet demonstrates the error:

```
import std.stdio;

final class BTree(
    ValueT, KeyT = ValueT,
	const(KeyT) function(ValueT) @safe @nogc nothrow pure KeyF = (a) => a) {
	
	KeyT getKey(ValueT val) {
		return KeyF(val);
	}
}

void main()
{
    auto btree1 = new BTree!(char);  // Removing this line eliminates the error.
    auto btree2 = new BTree!(int);
}
```

The error is:
```
onlineapp.d(8): Error: function literal `__lambda6(char a)` is not callable using argument types `(int)`
onlineapp.d(8):        cannot pass argument `val` of type `int` to parameter `char a`
onlineapp.d(15): Error: template instance `onlineapp.BTree!(int, int, function (char a) => a)` error instantiating
```

Is this an error in the compiler or in my own understanding of the D language?
May 27, 2018
On Saturday, 26 May 2018 at 11:56:30 UTC, Vijay Nayar wrote:
> The error is:
> ```
> onlineapp.d(8): Error: function literal `__lambda6(char a)` is not callable using argument types `(int)`
> onlineapp.d(8):        cannot pass argument `val` of type `int` to parameter `char a`
> onlineapp.d(15): Error: template instance `onlineapp.BTree!(int, int, function (char a) => a)` error instantiating
> ```

Just to clarify.  In the example above, if I create a 'BTree!int' by itself, it's fine.  If I create a 'BTree!char' by itself, it's fine also.  But if I create both, even if they are created in different modules, the compiler seems to mix up the types of the function template-parameter, and tries to fit a 'char' to the 'int' function or an 'int' to the 'char' function, depending on which was declared first.
May 27, 2018
I would rewrite it to something like this:

import std.stdio;
import std.functional;

template BTree(ValueT, KeyT = ValueT,alias KeyF = unaryFun!"cast(const)a")
{
    class BTree
    {
        auto getKey(ValueT val) {
            return KeyF(val);
        }
    }
}

void main()
{
    auto btree1 = new BTree!(char)();  // Removing this line eliminates the
error.
    auto btree2 = new BTree!(int)();
}

On Sun, May 27, 2018 at 9:03 AM, Vijay Nayar via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> On Saturday, 26 May 2018 at 11:56:30 UTC, Vijay Nayar wrote:
>
>> The error is:
>> ```
>> onlineapp.d(8): Error: function literal `__lambda6(char a)` is not
>> callable using argument types `(int)`
>> onlineapp.d(8):        cannot pass argument `val` of type `int` to
>> parameter `char a`
>> onlineapp.d(15): Error: template instance `onlineapp.BTree!(int, int,
>> function (char a) => a)` error instantiating
>> ```
>>
>
> Just to clarify.  In the example above, if I create a 'BTree!int' by itself, it's fine.  If I create a 'BTree!char' by itself, it's fine also. But if I create both, even if they are created in different modules, the compiler seems to mix up the types of the function template-parameter, and tries to fit a 'char' to the 'int' function or an 'int' to the 'char' function, depending on which was declared first.
>


May 28, 2018
On Sunday, 27 May 2018 at 20:38:25 UTC, Daniel Kozak wrote:
> I would rewrite it to something like this:
>
> template BTree(ValueT, KeyT = ValueT,alias KeyF = unaryFun!"cast(const)a")
> {
>     class BTree
>     {

This is roughly what I originally had, but it creates a number of problems that I wanted to get around.  Changing KeyF back to an alias means that any function that uses it can no longer be const, pure, @nogc, or nothrow.  Essentially the parameter is just anything the user provides.

If I use a template value-parameter, then it forces any lambda the user passes in to either match the type I enter in (with const, pure, etc.) or the program to fail to compile.  That is, I don't want the user to pass in any function, but only functions with the desired attributes.  I.e., I wouldn't want them to pass in for KeyF something like "a.data--".

Listing out the full type does indeed work correctly with various examples, and letting the user pass in something like `a => a._id` does compile, but the only problem is that when there are two such template instances in the same program.

Logically `BTree!(MyStruct, int, a => a.id)`, `BTree!(AnotherStruct, char, a => a.name[0])`, `BTree!int` and `BTree!char` should all be totally independent.  But for reasons unknown, the individual parameters seems to be swapped and and confused during compilation.

In the error above I listed.  The function parameter from `BTree!char` is being used to create a compile error against `BTree!int`, which is very odd.  Each of these classes compile and run just fine individually, the compilation only breaks when both exist.


May 29, 2018
On Saturday, 26 May 2018 at 11:56:30 UTC, Vijay Nayar wrote:
> I've been experimenting with code that uses std.functional : binaryFun and unaryFun, but I have found that using these methods makes it impossible to add function attributes like @safe, @nogc, pure, and nothrow, because no guarantee can be made about the functions created via a stream.  For example, if you expect a comparator function like "a == b", someone can pass in "a.data--" instead.
>
> [...]

This is probably a bug. BTree!char.lambda is clearly not the same as BTree!int.lambda, but the compiler seems to disagree?
May 29, 2018
On Tuesday, 29 May 2018 at 11:34:03 UTC, Yuxuan Shui wrote:
> On Saturday, 26 May 2018 at 11:56:30 UTC, Vijay Nayar wrote:
>> I've been experimenting with code that uses std.functional : binaryFun and unaryFun, but I have found that using these methods makes it impossible to add function attributes like @safe, @nogc, pure, and nothrow, because no guarantee can be made about the functions created via a stream.  For example, if you expect a comparator function like "a == b", someone can pass in "a.data--" instead.
>>
>> [...]
>
> This is probably a bug. BTree!char.lambda is clearly not the same as BTree!int.lambda, but the compiler seems to disagree?

No, wait a second. (a)=>a is in default argument list, so it is in the global scope. And it was instantiated when you instantiate BTree with char.
May 29, 2018
On Tuesday, 29 May 2018 at 11:36:11 UTC, Yuxuan Shui wrote:
>
> No, wait a second. (a)=>a is in default argument list, so it is in the global scope. And it was instantiated when you instantiate BTree with char.

Could you explain that part a bit for me?  Yes, (a) => a is a default value, but when you say it is in the global scope, are you saying that a single object "(a) => a" is created in the global scope and not created for each template argument list, e.g. "BTree!int" and "BTree!char"?

I actually do not know in what scope such objects would be created, I had assumed it was per template-parameter list, but are you saying this is not the case?
May 29, 2018
On Tuesday, 29 May 2018 at 12:37:04 UTC, Vijay Nayar wrote:
> On Tuesday, 29 May 2018 at 11:36:11 UTC, Yuxuan Shui wrote:
>>
>> No, wait a second. (a)=>a is in default argument list, so it is in the global scope. And it was instantiated when you instantiate BTree with char.
>
> Could you explain that part a bit for me?  Yes, (a) => a is a default value, but when you say it is in the global scope, are you saying that a single object "(a) => a" is created in the global scope and not created for each template argument list, e.g. "BTree!int" and "BTree!char"?
>
> I actually do not know in what scope such objects would be created, I had assumed it was per template-parameter list, but are you saying this is not the case?

I believe that is the case. Normally that will be fine, because you can't modify them. Type-deduced lambda is a very special case, as in their parameter types are deduced on first use, so in a sense, they are "modified" by the first instantiation.

BTW, I can't find the documentation about defining lambda with their parameter types omitted anywhere.
May 29, 2018
On Tuesday, 29 May 2018 at 12:58:20 UTC, Yuxuan Shui wrote:

> I believe that is the case. Normally that will be fine, because you can't modify them. Type-deduced lambda is a very special case, as in their parameter types are deduced on first use, so in a sense, they are "modified" by the first instantiation.
>
> BTW, I can't find the documentation about defining lambda with their parameter types omitted anywhere.

I tried this again, this time completely ignoring lambdas and completely specifying the desired type like so:

final class BTree(
    ValueT,
    KeyT = ValueT,
    const(KeyT) function(ValueT) nothrow pure @nogc KeyF =
        function KeyT(ValueT a) { return a; }) {
	
  KeyT getKey(ValueT val) {
    return KeyF(val);
  }
}

But unfortunately, the following code still produces an error:

void main()
{
    auto btree1 = new BTree!(char);
    auto btree2 = new BTree!(int);  // The error is on this line.
}

onlineapp.d(17): Error: template instance `BTree!int` does not match template declaration `BTree(ValueT, KeyT = ValueT, const(char) function(char) pure nothrow @nogc KeyF = function KeyT(ValueT a)
{
return a;
}
)`

I think at this point this may be a bug in the compiler.  What do you think?

May 30, 2018
On Tuesday, 29 May 2018 at 19:17:37 UTC, Vijay Nayar wrote:
> On Tuesday, 29 May 2018 at 12:58:20 UTC, Yuxuan Shui wrote:
>
>> [...]
>
> I tried this again, this time completely ignoring lambdas and completely specifying the desired type like so:
>
> [...]

Issue created:  https://issues.dlang.org/show_bug.cgi?id=18917