Thread overview
Trying to understand map being a template
Jan 05, 2024
Noé Falzon
Jan 05, 2024
H. S. Teoh
Jan 06, 2024
Paul Backus
Jan 06, 2024
FeepingCreature
Jan 09, 2024
Noé Falzon
January 05, 2024

On the subject of map taking the function as template parameter, I was surprised to see it could still be used with functions determined at runtime, even closures, etc. I am trying to understand the mechanism behind it.

The commented out line causes the error that choice(funcs) cannot be determined at compile time, which is fair, but how is it not the same case for func two lines above? I thought it might be because the functions are literals visible at compile time, but the closure makes that dubious.

import std.stdio;
import std.algorithm;
import std.random;

void main()
{
	int r = uniform(0,100);

	int delegate(int)[] funcs = [
		x => x * 2,
		x => x * x,
		x => 3,
		x => x * r		// Closure
	];

	auto foo = [1,2,3,4,5];

	foreach(i; 0..10)
	{
		// This is fine:
		auto func = funcs.choice;
		writeln(foo.map!func);

		// This is not?
		// writeln(foo.map!(funcs.choice));
	}
}

In fact, how can the template be instantiated at all in the following example, where no functions can possibly be known at compile time:

auto do_random_map(int delegate(int)[] funcs, int[] values)
{
	auto func = funcs.choice;
	return values.map!func;
}

Thank you for the insights!

January 05, 2024
On Fri, Jan 05, 2024 at 08:41:53PM +0000, Noé Falzon via Digitalmars-d-learn wrote:
> On the subject of `map` taking the function as template parameter, I was surprised to see it could still be used with functions determined at runtime, even closures, etc. I am trying to understand the mechanism behind it.

That's simple, if the argument is a runtime function, it is treated as a function pointer (or delegate).


[...]
> In fact, how can the template be instantiated at all in the following example, where no functions can possibly be known at compile time:
> 
> ```
> auto do_random_map(int delegate(int)[] funcs, int[] values)
> {
> 	auto func = funcs.choice;
> 	return values.map!func;
> }
> ```
[...]

The argument is taken to be a delegate to be bound at runtime. In the instantiation a shim is inserted to pass along the delegate from the caller's context.


T

-- 
Creativity is not an excuse for sloppiness.
January 06, 2024

On Friday, 5 January 2024 at 20:41:53 UTC, Noé Falzon wrote:

>

In fact, how can the template be instantiated at all in the following example, where no functions can possibly be known at compile time:

auto do_random_map(int delegate(int)[] funcs, int[] values)
{
	auto func = funcs.choice;
	return values.map!func;
}

Thank you for the insights!

It works for the same reason this example works:

void printVar(alias var)()
{
    import std.stdio;
    writeln(__traits(identifier, var), " = ", var);
}

void main()
{
    int x = 123;
    int y = 456;

    printVar!x; // x = 123
    printVar!y; // y = 456
    x = 789;
    printVar!x; // x = 789
}
January 06, 2024

On Saturday, 6 January 2024 at 17:57:06 UTC, Paul Backus wrote:

>

On Friday, 5 January 2024 at 20:41:53 UTC, Noé Falzon wrote:

>

In fact, how can the template be instantiated at all in the following example, where no functions can possibly be known at compile time:

auto do_random_map(int delegate(int)[] funcs, int[] values)
{
	auto func = funcs.choice;
	return values.map!func;
}

Thank you for the insights!

It works for the same reason this example works:

void printVar(alias var)()
{
    import std.stdio;
    writeln(__traits(identifier, var), " = ", var);
}

void main()
{
    int x = 123;
    int y = 456;

    printVar!x; // x = 123
    printVar!y; // y = 456
    x = 789;
    printVar!x; // x = 789
}

To clarify, what this actually compiles to is:


void main()
{
  int x = 123;
  int y = 456;
  void printVar_x()
  {
     import std.stdio;
     writeln(__traits(identifier, x), " = ", x);
  }
  void printVar_y()
  {
     import std.stdio;
     writeln(__traits(identifier, y), " = ", y);
  }
  printVar_x;
  printVar_y;
  x = 789;
  printVar_x;
}

Which lowers to:

struct mainStackframe
{
  int x;
  int y;
}

void printVar_main_x(mainStackframe* context)
{
   import std.stdio;
   writeln(__traits(identifier, context.x), " = ", context.x);
}

void printVar_main_y(mainStackframe* context)
{
   import std.stdio;
   writeln(__traits(identifier, context.y), " = ", context.y);
}

void main()
{
  // this is the only "actual" variable in main()
  mainStackframe frame;
  frame.x = 123;
  frame.y = 456;
  printVar_main_x(&frame);
  printVar_main_y(&frame);
  frame.x = 789;
  printVar_main_x(&frame);
}


Same with `map`.
January 09, 2024

Thank you very much for your answers. The point I had mostly misunderstood was alias template parameters, which make this possible.