Thread overview
Changing Template Static Ifs to Recursion
May 31, 2017
jmh530
May 31, 2017
Stefan Koch
May 31, 2017
jmh530
Jun 01, 2017
jmh530
May 31, 2017
ag0aep6g
May 31, 2017
jmh530
May 31, 2017
I have a struct that I am using like a Tuple, but I want to be able to opIndex in a different way than Tuple's opIndex. I want to be able to opIndex whatever is underlying the Tuple.

The code below works, but is kind of annoying because to extend you have to keep adding static ifs. I want to change it to a recursion that can handle any length. I tried a few different options, but not having much luck.

Would appreciate any advice!

Note: I left out the function foo, but think of foo is to Foo as tuple is to Tuple.

import std.typecons : Tuple;

struct Foo(T...)
{
	alias U = Tuple!T;
	U underlying;
	alias underlying this;
	
	alias Names = U.fieldNames;
	alias Types = U.Types;
	
	template process(B...)
	{
		auto ref process(A...)(A a)
		{
			alias fooB = foo!(B);
			
			static if (A.length == 1)
			{
				return fooB(this.underlying[0][a[0]]);
			}
			else static if (A.length == 2)
			{
				return fooB(this.underlying[0][a[0]],
					    this.underlying[1][a[1]]);
			}
			else static if (A.length == 3)
			{
				return fooB(this.underlying[0][a[0]],
					    this.underlying[1][a[1]],
					    this.underlying[2][a[2]]);
			}
		}
	}
	
	auto ref opIndex(Slices...)(Slices slices)
		if (Slices.length == Types.length)
	{
		return process!(Names)(slices);
	}
}
May 31, 2017
On Wednesday, 31 May 2017 at 18:50:27 UTC, jmh530 wrote:
> I have a struct that I am using like a Tuple, but I want to be able to opIndex in a different way than Tuple's opIndex. I want to be able to opIndex whatever is underlying the Tuple.
>
> [...]

You could also use string mixins.
Which will be more efficient then recursion.
May 31, 2017
On 05/31/2017 08:50 PM, jmh530 wrote:
> Note: I left out the function foo, but think of foo is to Foo as tuple is to Tuple.

You should have included foo, in my opinion. I'm having trouble figuring out what your code does. `process` instantiates foo with the field names. I'd need the definition of foo to make sense of that.

A usage example of the whole thing would also help.

> import std.typecons : Tuple;
> 
> struct Foo(T...)
> {
>      alias U = Tuple!T;
>      U underlying;
>      alias underlying this;
> 
>      alias Names = U.fieldNames;
>      alias Types = U.Types;
> 
>      template process(B...)
>      {
>          auto ref process(A...)(A a)
>          {
>              alias fooB = foo!(B);
> 
>              static if (A.length == 1)
>              {
>                  return fooB(this.underlying[0][a[0]]);
>              }
>              else static if (A.length == 2)
>              {
>                  return fooB(this.underlying[0][a[0]],
>                          this.underlying[1][a[1]]);
>              }
>              else static if (A.length == 3)
>              {
>                  return fooB(this.underlying[0][a[0]],
>                          this.underlying[1][a[1]],
>                          this.underlying[2][a[2]]);
>              }
>          }
>      }
> 
>      auto ref opIndex(Slices...)(Slices slices)
>          if (Slices.length == Types.length)
>      {
>          return process!(Names)(slices);
>      }
> }

I've pieced something together, but I'm not sure if it's what you're looking for. Note that I've changed how foo is instantiated, because it isn't obvious to me how your version works.

----
import std.typecons : Tuple;

struct Foo(T...)
{
    alias U = Tuple!T;
    U underlying;
    alias underlying this;

    alias Names = U.fieldNames;
    alias Types = U.Types;

    auto ref opIndex(Slices...)(Slices slices)
        if (Slices.length == Types.length)
    {
        import std.meta: staticMap;
        alias ElementType(A : E[], E) = E;
        alias R = staticMap!(ElementType, Types);
        R result;
        foreach (i, Ignored; Slices)
        {
            result[i] = this.underlying[i][slices[i]];
        }
        return foo(result);
    }
}

Foo!T foo(T ...)(T stuff) { return Foo!T(Tuple!T(stuff)); }

void main()
{
    auto f = foo([1, 2, 3], [4, 5, 6]);
    auto e = f[1, 2];
    assert(e == foo(2, 6));
}
----
May 31, 2017
On Wednesday, 31 May 2017 at 19:25:22 UTC, ag0aep6g wrote:
> On 05/31/2017 08:50 PM, jmh530 wrote:
>> Note: I left out the function foo, but think of foo is to Foo as tuple is to Tuple.
>
> You should have included foo, in my opinion. I'm having trouble figuring out what your code does. `process` instantiates foo with the field names. I'd need the definition of foo to make sense of that.
>

Sorry, I'm glad you understood what I meant. I only needed to make one small change it worked perfectly. Cheers.
May 31, 2017
On Wednesday, 31 May 2017 at 19:22:18 UTC, Stefan Koch wrote:
>
> You could also use string mixins.
> Which will be more efficient then recursion.

I try to avoid string mixins unless I can't help it. Nevertheless, I made an effort to try to get it to work and below seems to be working. I still have to use some recursion to generate the string.

The downside is that this version can't be @nogc, correct?


auto ref opIndex(Slices...)(Slices slices)
	if (Slices.length == Types.length)
{
	template GetParensi(size_t i)
	{
		const char[] GetParensi = "this.underlying[" ~ i.stringof ~
										"][slices[" ~ i.stringof ~ "]]";
	}

	template GenParens(T...)
	{
		static if (T.length > 0)
		{
			static if (T.length == 1)
				const char[] GenParens = GetParensi!(T.length - 1);
			else static if (T.length > 1)
				const char[] GenParens = GenParens!(T[0 .. $ - 1]) ~ ", " ~
												GetParensi!(T.length - 1);
		}
	}
	
	const char[] GenResult = "foo!(Names)(" ~ GenParens!Slices ~ ")";
	
	return mixin(GenResult);
}
June 01, 2017
On Wednesday, 31 May 2017 at 21:02:07 UTC, jmh530 wrote:
> On Wednesday, 31 May 2017 at 19:22:18 UTC, Stefan Koch wrote:
>>
>> You could also use string mixins.
>> Which will be more efficient then recursion.
>
> I try to avoid string mixins unless I can't help it. Nevertheless, I made an effort to try to get it to work and below seems to be working. I still have to use some recursion to generate the string.
>
> The downside is that this version can't be @nogc, correct?
>

One advantage of the mixin version is that I don't have to explicitly get the type of the result. In ag0aep6g's example, it works easily enough for arrays, but if the indexing is more complicated, then it becomes a pain to get the correct types.

I struggled a little to get it @nogc, but eventually got it there by changing the helper code to free-standing functions that use enums to force CTFE. The code is below:

private
{
	const(char[]) opIndex_i(size_t i)() @nogc
	{
		return "this.underlying[" ~ i.stringof ~ "][slices[" ~
									i.stringof ~ "]]";
	}
		
	const(char[]) GenResult(alias f, T...)() @nogc
	{
		const(char[]) GenParens(U...)() @nogc
		{
			static if (U.length > 0)
			{
				static if (U.length == 1)
				{
					enum result = f!(U.length - 1);
				}
				else static if (U.length > 1)
				{
					enum result = GenParens!(U[0 .. $ - 1]) ~ ", " ~ f!(U.length - 1);
				}
				return result;
			}
		}
		
		enum parens = GenParens!(T);
		return "foo!(Names)(" ~ parens ~ ")";
	}
}

struct Foo(T...)
{
	alias U = Tuple!T;
	U underlying;
	alias underlying this;
	
	alias Names = U.fieldNames;
	alias Types = U.Types;
	
	/// Alternate access to tuple
	auto index(size_t dimension)() const @property
	{
		return underlying[dimension];
	}
	
	auto ref opIndex(Slices...)(Slices slices) @nogc
        if (Slices.length == Types.length)
    {
		return mixin(GenResult!(opIndex_i, Slices));
    }
}