www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Changing Template Static Ifs to Recursion

reply jmh530 <john.michael.hall gmail.com> writes:
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
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
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
parent reply jmh530 <john.michael.hall gmail.com> writes:
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); }
May 31
parent jmh530 <john.michael.hall gmail.com> writes:
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)); } }
Jun 01
prev sibling parent reply ag0aep6g <anonymous example.com> writes:
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
parent jmh530 <john.michael.hall gmail.com> writes:
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