Thread overview
Understanding alias template parameters
Apr 21, 2022
JG
Apr 21, 2022
Ali Çehreli
Apr 21, 2022
Salih Dincer
Apr 21, 2022
Paul Backus
Apr 22, 2022
H. S. Teoh
Apr 22, 2022
JG
Apr 22, 2022
Ali Çehreli
Apr 22, 2022
Salih Dincer
April 21, 2022

Hi,

Could someone possibly help me to understand why the commented line doesn't compile?

import std;

struct MapResult(R,F)
{
    R r;
    const F f;
    auto empty() { return r.empty; }
    auto front() { return f(r.front); }
    void popFront() { r.popFront; }
    auto save() { return typeof(this)(r.save,f); }
}

auto myMap(alias f, R)(R r) {
    return MapResult!(R,typeof(f))(r,f);
}


void main()
{
   int function(int) f = x=>2*x;
   iota(10).myMap!f.writeln;
   //iota(10).myMap!(x=>2*x).writeln; <--- Why doesn't this compile?

}

(I do know that Phobos's map works differently with better performance).

April 21, 2022
On 4/21/22 14:02, JG wrote:

> Could someone possibly help me to understand why the commented line
> doesn't compile?

  iota(10).myMap!(x=>2*x).writeln;

It is because x=>2*x is just a template. I don't know why the compiler chooses 'void' for typeof(f) but apparently that's how it represents the types of function templates.

One way of changing the code is to use the following but it does not really fit because of course you may want types other than 'int':

  (int x) => 2 * x  // Now the type is known

The more logical thing to do is to stay with alias template parameters for MapResult as well. I am not sure why you need the member 'f' so I commented it out but now it compiles after some trivial changes:

import std;

struct MapResult(R, alias f)  // <== HERE
{
    R r;
    // const F f;
    auto empty() { return r.empty; }
    auto front() { return f(r.front); }
    void popFront() { r.popFront; }
    auto save() { return typeof(this)(r.save); }
}

auto myMap(alias f, R)(R r) {
  pragma(msg, typeof(f));
    return MapResult!(R, f)(r);
}


void main()
{
   int function(int) f = x=>2*x;
   iota(10).myMap!f.writeln;
   iota(10).myMap!(x=>2*x).writeln;
}

Ali

April 21, 2022

On Thursday, 21 April 2022 at 21:02:47 UTC, JG wrote:

>

Hi,

Could someone possibly help me to understand why the commented line doesn't compile?

import std;

struct MapResult(R,F)
{
    R r;
    const F f;
    auto empty() { return r.empty; }
    auto front() { return f(r.front); }
    void popFront() { r.popFront; }
    auto save() { return typeof(this)(r.save,f); }
}

auto myMap(alias f, R)(R r) {
    return MapResult!(R,typeof(f))(r,f);
}


void main()
{
   int function(int) f = x=>2*x;
   iota(10).myMap!f.writeln;
   //iota(10).myMap!(x=>2*x).writeln; <--- Why doesn't this compile?

}

When you write a lambda without type annotations, like x => 2*x, the compiler interprets it as a function template. So in this case, it's the same as if you'd written

T func(T)(T x) { return 2*x; }
iota(10).myMap!func.writeln;

The problem is, you can't store a template in a variable:

auto f = func; // doesn't work - func does not have a value

So, when you try to create a MapResult with the template x => 2*x as one of its member variables, it doesn't work.

By the way, you may have noticed that the error message you got says something about variables of type void not being allowed. That's because of a long-standing bug that causes typeof returns void when applied to a template. However, that bug is not the cause of the error here--even if it were fixed, your code still would not work.

April 21, 2022

On Thursday, 21 April 2022 at 21:38:14 UTC, Ali Çehreli wrote:

>
auto myMap(alias f, R)(R r) {
  pragma(msg, typeof(f));
    return MapResult!(R, f)(r);
}

It looks delicious when the convenience function works magic with Voldemort:

import std.range,  std.stdio;

auto myMap(alias f, R)(R r) {
  struct Map {
    auto empty() {
      return r.empty;
    }

    auto front() {
      return f(r.front);
    }

    void popFront() {
      r.popFront;
    }
  }
  return Map();
}

void main() {

  // with convenience function:

  alias func = (int x) =>  2 * x;
  auto range = 1.iota(11);

  range.myMap!func.writeln;
  range.myMap!(x => 2 * x).writeln;

  // with only struct:

  struct MapResult(alias range, alias f) {
    auto empty() { return range.empty; }
    auto front() { return f(range.front); }
    void popFront() { range.popFront; }
  }

  MapResult!(range, func) mp;
  foreach(result; mp) result.writeln;

} /* OUTPUT:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
2
4
6
8
10
12
14
16
18
20
*/

SDB@79

April 21, 2022
On Thu, Apr 21, 2022 at 09:02:47PM +0000, JG via Digitalmars-d-learn wrote:
> Hi,
> 
> Could someone possibly help me to understand why the commented line doesn't compile?
> 
> 
> ```d
> import std;
> 
> struct MapResult(R,F)
> {
>     R r;
>     const F f;
>     auto empty() { return r.empty; }
>     auto front() { return f(r.front); }
>     void popFront() { r.popFront; }
>     auto save() { return typeof(this)(r.save,f); }
> }
> 
> auto myMap(alias f, R)(R r) {
>     return MapResult!(R,typeof(f))(r,f);
> }
> 
> 
> void main()
> {
>    int function(int) f = x=>2*x;
>    iota(10).myMap!f.writeln;
>    //iota(10).myMap!(x=>2*x).writeln; <--- Why doesn't this compile?
> 
> }
> ```

Check the compiler's error message carefully:

/tmp/test.d(6): Error: variable `test.MapResult!(Result, void).MapResult.f` variables cannot be of type `void`
           ^^^
The error comes from line 6, which is:

>    const F f;

A lambda literal like `x=>2*x` is a template, so you cannot store it in a variable. You need to either explicitly wrap it in a delegate, or forward the alias to your struct as a template parameter, e.g.:

	struct MapResult(alias f, R)
	{
		... // remove the line `const F f;`
	}
	auto myMap(alias f, R)(R r) {
	    return MapResult!(f, R)(r);
	}


T

-- 
Truth, Sir, is a cow which will give [skeptics] no more milk, and so they are gone to milk the bull. -- Sam. Johnson
April 22, 2022

On Thursday, 21 April 2022 at 21:02:47 UTC, JG wrote:

>

Hi,

Could someone possibly help me to understand why the commented line doesn't compile?

import std;

struct MapResult(R,F)
{
    R r;
    const F f;
    auto empty() { return r.empty; }
    auto front() { return f(r.front); }
    void popFront() { r.popFront; }
    auto save() { return typeof(this)(r.save,f); }
}

auto myMap(alias f, R)(R r) {
    return MapResult!(R,typeof(f))(r,f);
}


void main()
{
   int function(int) f = x=>2*x;
   iota(10).myMap!f.writeln;
   //iota(10).myMap!(x=>2*x).writeln; <--- Why doesn't this compile?

}

(I do know that Phobos's map works differently with better performance).

Thank you to all for the great replies. To fix it one could do:

import std;

    struct MapResult(R,F) {
        R r;
        const F f;
        auto empty() { return r.empty; }
        auto front() { return f(r.front); }
        void popFront() { r.popFront; }
        auto save() { return typeof(this)(r.save,f); }
    }

    auto myMap(alias f, R)(R r) {
        static if(__traits(compiles,f!(typeof(R.init.front)))) {
            auto fun = f!(typeof(R.init.front));
            return MapResult!(R,typeof(fun))(r,fun);
        } else {
        return MapResult!(R,typeof(f))(r,f);
        }
    }


    void main()
    {
       int function(int) f = x=>2*x;
       iota(10).myMap!f.writeln;
       iota(10).myMap!(x=>2*x).writeln;

    }
    ```

In response to the change to "alias", which has several upsides
including faster code. I would note it also has some downsides including
every lambda produces a new type so that (at the moment) the following assert
holds:
```d
auto r = iota(10).map!(x=>x+1);
auto s = iota(10).map!(x=>x+1);
assert(!is(typeof(r)==typeof(s)));
April 22, 2022
On 4/22/22 01:04, JG wrote:

> In response to the change to "alias", which has several upsides
> including faster code. I would note it also has some downsides including
> every lambda produces a new type so that (at the moment) the following
> assert
> holds:

I got confused a little bit there. To make sure: alias would cause different template instantiation. That's why the following types are different for 'map'.

> ```d
> auto r = iota(10).map!(x=>x+1);
> auto s = iota(10).map!(x=>x+1);
> assert(!is(typeof(r)==typeof(s)));
> ```

When you use your MyMap, the range types are the same.

Having an alias parameter, myMap() function will still cause multiple instances but that's life.

Ali

April 22, 2022

On Friday, 22 April 2022 at 08:04:16 UTC, JG wrote:

>

On Thursday, 21 April 2022 at 21:02:47 UTC, JG wrote:

>

Hi,

Could someone possibly help me to understand why the commented line doesn't compile?

Good job, works great! Potential is high:

void main()
{
  alias type = real;
  auto range = iota!type(1, 4, .5);

  alias fun1 = (type a) => a * a;
  alias fun2 = (type a) => a * 2;
  alias fun3 = (type a) => a + 1;
  alias fun4 = (type a) => a - 1;
  alias fun5 = (type a) => a / 2;

  alias func = type function(type a);
  alias MR = MapResult!(typeof(range), func);

  MR[] mr;
  tuple(fun1, fun2, fun3, fun4, fun5)
       .each!(f => mr ~= MR(range, f));

  mr.writefln!"%-(%s\n%)";

} /* OUTPUT:
[1, 2.25, 4, 6.25, 9, 12.25]
[2, 3, 4, 5, 6, 7]
[2, 2.5, 3, 3.5, 4, 4.5]
[0, 0.5, 1, 1.5, 2, 2.5]
[0.5, 0.75, 1, 1.25, 1.5, 1.75]
*/