1 /++ 2 Functions that manipulate other functions. 3 This module provides functions for compile time function composition. These 4 functions are helpful when constructing predicates for the algorithms in 5 $(MREF mir, ndslice). 6 $(BOOKTABLE $(H2 Functions), 7 $(TR $(TH Function Name) $(TH Description)) 8 $(TR $(TD $(LREF naryFun)) 9 $(TD Create a unary, binary or N-nary function from a string. Most often 10 used when defining algorithms on ranges and slices. 11 )) 12 $(TR $(TD $(LREF pipe)) 13 $(TD Join a couple of functions into one that executes the original 14 functions one after the other, using one function's result for the next 15 function's argument. 16 )) 17 $(TR $(TD $(LREF not)) 18 $(TD Creates a function that negates another. 19 )) 20 $(TR $(TD $(LREF reverseArgs)) 21 $(TD Predicate that reverses the order of its arguments. 22 )) 23 $(TR $(TD $(LREF forward)) 24 $(TD Forwards function arguments with saving ref-ness. 25 )) 26 $(TR $(TD $(LREF refTuple)) 27 $(TD Removes $(LREF Ref) shell. 28 )) 29 $(TR $(TD $(LREF unref)) 30 $(TD Creates a $(LREF RefTuple) structure. 31 )) 32 $(TR $(TD $(LREF __ref)) 33 $(TD Creates a $(LREF Ref) structure. 34 )) 35 ) 36 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 37 Authors: Ilya Yaroshenko, $(HTTP erdani.org, Andrei Alexandrescu (some original code from std.functional)) 38 39 Macros: 40 NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) 41 +/ 42 module mir.functional; 43 44 private enum isRef(T) = is(T : Ref!T0, T0); 45 46 import mir.math.common: optmath; 47 48 public import core.lifetime : forward; 49 50 @optmath: 51 52 /++ 53 Constructs static array. 54 +/ 55 T[N] staticArray(T, size_t N)(T[N] a...) 56 { 57 return a; 58 } 59 60 /++ 61 Simple wrapper that holds a pointer. 62 It is used for as workaround to return multiple auto ref values. 63 +/ 64 struct Ref(T) 65 if (!isRef!T) 66 { 67 @optmath: 68 69 @disable this(); 70 /// 71 this(ref T value) @trusted 72 { 73 __ptr = &value; 74 } 75 /// 76 T* __ptr; 77 /// 78 ref inout(T) __value() inout @property { return *__ptr; } 79 /// 80 alias __value this; 81 /// 82 bool opEquals(scope Ref!T rhs) const scope 83 { 84 return __value == rhs.__value; 85 } 86 87 static if (__traits(hasMember, T, "toHash") || __traits(isScalar, T)) 88 /// 89 size_t toHash() const 90 { 91 return hashOf(__value); 92 } 93 } 94 95 /// Creates $(LREF Ref) wrapper. 96 Ref!T _ref(T)(ref T value) 97 { 98 return Ref!T(value); 99 } 100 101 private mixin template _RefTupleMixin(T...) 102 if (T.length <= 26) 103 { 104 static if (T.length) 105 { 106 enum i = T.length - 1; 107 static if (isRef!(T[i])) 108 mixin(`@optmath @property ref ` ~ cast(char)('a' + i) ~ `() { return *expand[` ~ i.stringof ~ `].__ptr; }` ); 109 else 110 mixin(`alias ` ~ cast(char)('a' + i) ~ ` = expand[` ~ i.stringof ~ `];`); 111 mixin ._RefTupleMixin!(T[0 .. $-1]); 112 } 113 } 114 115 /++ 116 Simplified tuple structure. Some fields may be type of $(LREF Ref). 117 Ref stores a pointer to a values. 118 +/ 119 struct RefTuple(T...) 120 { 121 @optmath: 122 T expand; 123 alias expand this; 124 mixin _RefTupleMixin!T; 125 } 126 127 /// Removes $(LREF Ref) shell. 128 alias Unref(V : Ref!T, T) = T; 129 /// ditto 130 template Unref(V : RefTuple!T, T...) 131 { 132 import std.meta: staticMap; 133 alias Unref = RefTuple!(staticMap!(.Unref, T)); 134 } 135 136 /// ditto 137 alias Unref(V) = V; 138 139 /++ 140 Returns: a $(LREF RefTuple) structure. 141 +/ 142 RefTuple!Args refTuple(Args...)(auto ref Args args) 143 { 144 return RefTuple!Args(args); 145 } 146 147 /// Removes $(LREF Ref) shell. 148 ref T unref(V : Ref!T, T)(scope return V value) 149 { 150 return *value.__ptr; 151 } 152 153 /// ditto 154 Unref!(RefTuple!T) unref(V : RefTuple!T, T...)(V value) 155 { 156 typeof(return) ret; 157 foreach(i, ref elem; ret.expand) 158 elem = unref(value.expand[i]); 159 return ret; 160 } 161 162 /// ditto 163 ref V unref(V)(scope return ref V value) 164 { 165 return value; 166 } 167 168 /// ditto 169 V unref(V)(V value) 170 { 171 import std.traits: hasElaborateAssign; 172 static if (hasElaborateAssign!V) 173 { 174 import core.lifetime: move; 175 return move(value); 176 } 177 else 178 return value; 179 } 180 181 private string joinStrings()(string[] strs) 182 { 183 if (strs.length) 184 { 185 auto ret = strs[0]; 186 foreach(s; strs[1 .. $]) 187 ret ~= s; 188 return ret; 189 } 190 return null; 191 } 192 193 /++ 194 Takes multiple functions and adjoins them together. The result is a 195 $(LREF RefTuple) with one element per passed-in function. Upon 196 invocation, the returned tuple is the adjoined results of all 197 functions. 198 Note: In the special case where only a single function is provided 199 (`F.length == 1`), adjoin simply aliases to the single passed function 200 (`F[0]`). 201 +/ 202 template adjoin(fun...) if (fun.length && fun.length <= 26) 203 { 204 static if (fun.length != 1) 205 { 206 import std.meta: staticMap, Filter; 207 static if (Filter!(_needNary, fun).length == 0) 208 { 209 /// 210 @optmath auto adjoin(Args...)(auto ref Args args) 211 { 212 template _adjoin(size_t i) 213 { 214 static if (__traits(compiles, &fun[i](forward!args))) 215 enum _adjoin = "Ref!(typeof(fun[" ~ i.stringof ~ "](forward!args)))(fun[" ~ i.stringof ~ "](forward!args)), "; 216 else 217 enum _adjoin = "fun[" ~ i.stringof ~ "](forward!args), "; 218 } 219 220 import mir.internal.utility; 221 mixin("return refTuple(" ~ [staticMap!(_adjoin, Iota!(fun.length))].joinStrings ~ ");"); 222 } 223 } 224 else alias adjoin = .adjoin!(staticMap!(naryFun, fun)); 225 } 226 else alias adjoin = naryFun!(fun[0]); 227 } 228 229 /// 230 @safe version(mir_core_test) unittest 231 { 232 static bool f1(int a) { return a != 0; } 233 static int f2(int a) { return a / 2; } 234 auto x = adjoin!(f1, f2)(5); 235 assert(is(typeof(x) == RefTuple!(bool, int))); 236 assert(x.a == true && x.b == 2); 237 } 238 239 @safe version(mir_core_test) unittest 240 { 241 static bool F1(int a) { return a != 0; } 242 auto x1 = adjoin!(F1)(5); 243 static int F2(int a) { return a / 2; } 244 auto x2 = adjoin!(F1, F2)(5); 245 assert(is(typeof(x2) == RefTuple!(bool, int))); 246 assert(x2.a && x2.b == 2); 247 auto x3 = adjoin!(F1, F2, F2)(5); 248 assert(is(typeof(x3) == RefTuple!(bool, int, int))); 249 assert(x3.a && x3.b == 2 && x3.c == 2); 250 251 bool F4(int a) { return a != x1; } 252 alias eff4 = adjoin!(F4); 253 static struct S 254 { 255 bool delegate(int) @safe store; 256 int fun() { return 42 + store(5); } 257 } 258 S s; 259 s.store = (int a) { return eff4(a); }; 260 auto x4 = s.fun(); 261 assert(x4 == 43); 262 } 263 264 //@safe 265 version(mir_core_test) unittest 266 { 267 import std.meta: staticMap; 268 alias funs = staticMap!(naryFun, "a", "a * 2", "a * 3", "a * a", "-a"); 269 alias afun = adjoin!funs; 270 int a = 5, b = 5; 271 assert(afun(a) == refTuple(Ref!int(a), 10, 15, 25, -5)); 272 assert(afun(a) == refTuple(Ref!int(b), 10, 15, 25, -5)); 273 274 static class C{} 275 alias IC = immutable(C); 276 IC foo(){return typeof(return).init;} 277 RefTuple!(IC, IC, IC, IC) ret1 = adjoin!(foo, foo, foo, foo)(); 278 279 static struct S{int* p;} 280 alias IS = immutable(S); 281 IS bar(){return typeof(return).init;} 282 enum RefTuple!(IS, IS, IS, IS) ret2 = adjoin!(bar, bar, bar, bar)(); 283 } 284 285 private template needOpCallAlias(alias fun) 286 { 287 /* Determine whether or not naryFun need to alias to fun or 288 * fun.opCall. Basically, fun is a function object if fun(...) compiles. We 289 * want is(naryFun!fun) (resp., is(naryFun!fun)) to be true if fun is 290 * any function object. There are 4 possible cases: 291 * 292 * 1) fun is the type of a function object with static opCall; 293 * 2) fun is an instance of a function object with static opCall; 294 * 3) fun is the type of a function object with non-static opCall; 295 * 4) fun is an instance of a function object with non-static opCall. 296 * 297 * In case (1), is(naryFun!fun) should compile, but does not if naryFun 298 * aliases itself to fun, because typeof(fun) is an error when fun itself 299 * is a type. So it must be aliased to fun.opCall instead. All other cases 300 * should be aliased to fun directly. 301 */ 302 static if (is(typeof(fun.opCall) == function)) 303 { 304 import std.traits: Parameters; 305 enum needOpCallAlias = !is(typeof(fun)) && __traits(compiles, () { 306 return fun(Parameters!fun.init); 307 }); 308 } 309 else 310 enum needOpCallAlias = false; 311 } 312 313 private template _naryAliases(size_t n) 314 if (n <= 26) 315 { 316 static if (n == 0) 317 enum _naryAliases = ""; 318 else 319 { 320 enum i = n - 1; 321 enum _naryAliases = _naryAliases!i ~ "alias " ~ cast(char)('a' + i) ~ " = args[" ~ i.stringof ~ "];\n"; 322 } 323 } 324 325 /++ 326 Aliases itself to a set of functions. 327 328 Transforms strings representing an expression into a binary function. The 329 strings must use symbol names `a`, `b`, ..., `z` as the parameters. 330 If `functions[i]` is not a string, `naryFun` aliases itself away to `functions[i]`. 331 +/ 332 template naryFun(functions...) 333 if (functions.length >= 1) 334 { 335 static foreach (fun; functions) 336 { 337 static if (is(typeof(fun) : string)) 338 { 339 import mir.math.common; 340 /// Specialization for string lambdas 341 @optmath auto ref naryFun(Args...)(auto ref Args args) 342 if (args.length <= 26) 343 { 344 mixin(_naryAliases!(Args.length)); 345 return mixin(fun); 346 } 347 } 348 else static if (needOpCallAlias!fun) 349 alias naryFun = fun.opCall; 350 else 351 alias naryFun = fun; 352 } 353 } 354 355 /// 356 @safe version(mir_core_test) unittest 357 { 358 // Strings are compiled into functions: 359 alias isEven = naryFun!("(a & 1) == 0"); 360 assert(isEven(2) && !isEven(1)); 361 } 362 363 /// 364 @safe version(mir_core_test) unittest 365 { 366 alias less = naryFun!("a < b"); 367 assert(less(1, 2) && !less(2, 1)); 368 alias greater = naryFun!("a > b"); 369 assert(!greater("1", "2") && greater("2", "1")); 370 } 371 372 /// `naryFun` accepts up to 26 arguments. 373 @safe version(mir_core_test) unittest 374 { 375 assert(naryFun!("a * b + c")(2, 3, 4) == 10); 376 } 377 378 /// `naryFun` can return by reference. 379 version(mir_core_test) unittest 380 { 381 int a; 382 assert(&naryFun!("a")(a) == &a); 383 } 384 385 /// `args` parameter tuple 386 version(mir_core_test) unittest 387 { 388 assert(naryFun!("args[0] + args[1]")(2, 3) == 5); 389 } 390 391 /// Multiple functions 392 @safe pure nothrow @nogc 393 version(mir_core_test) unittest 394 { 395 alias fun = naryFun!( 396 (uint a) => a, 397 (ulong a) => a * 2, 398 a => a * 3, 399 ); 400 401 int a = 10; 402 long b = 10; 403 float c = 10; 404 405 assert(fun(a) == 10); 406 assert(fun(b) == 20); 407 assert(fun(c) == 30); 408 } 409 410 @safe version(mir_core_test) unittest 411 { 412 static int f1(int a) { return a + 1; } 413 static assert(is(typeof(naryFun!(f1)(1)) == int)); 414 assert(naryFun!(f1)(41) == 42); 415 int f2(int a) { return a + 1; } 416 static assert(is(typeof(naryFun!(f2)(1)) == int)); 417 assert(naryFun!(f2)(41) == 42); 418 assert(naryFun!("a + 1")(41) == 42); 419 420 int num = 41; 421 assert(naryFun!"a + 1"(num) == 42); 422 423 // Issue 9906 424 struct Seen 425 { 426 static bool opCall(int n) { return true; } 427 } 428 static assert(needOpCallAlias!Seen); 429 static assert(is(typeof(naryFun!Seen(1)))); 430 assert(naryFun!Seen(1)); 431 432 Seen s; 433 static assert(!needOpCallAlias!s); 434 static assert(is(typeof(naryFun!s(1)))); 435 assert(naryFun!s(1)); 436 437 struct FuncObj 438 { 439 bool opCall(int n) { return true; } 440 } 441 FuncObj fo; 442 static assert(!needOpCallAlias!fo); 443 static assert(is(typeof(naryFun!fo))); 444 assert(naryFun!fo(1)); 445 446 // Function object with non-static opCall can only be called with an 447 // instance, not with merely the type. 448 static assert(!is(typeof(naryFun!FuncObj))); 449 } 450 451 @safe version(mir_core_test) unittest 452 { 453 static int f1(int a, string b) { return a + 1; } 454 static assert(is(typeof(naryFun!(f1)(1, "2")) == int)); 455 assert(naryFun!(f1)(41, "a") == 42); 456 string f2(int a, string b) { return b ~ "2"; } 457 static assert(is(typeof(naryFun!(f2)(1, "1")) == string)); 458 assert(naryFun!(f2)(1, "4") == "42"); 459 assert(naryFun!("a + b")(41, 1) == 42); 460 //@@BUG 461 //assert(naryFun!("return a + b;")(41, 1) == 42); 462 463 // Issue 9906 464 struct Seen 465 { 466 static bool opCall(int x, int y) { return true; } 467 } 468 static assert(is(typeof(naryFun!Seen))); 469 assert(naryFun!Seen(1,1)); 470 471 struct FuncObj 472 { 473 bool opCall(int x, int y) { return true; } 474 } 475 FuncObj fo; 476 static assert(!needOpCallAlias!fo); 477 static assert(is(typeof(naryFun!fo))); 478 assert(naryFun!fo(1,1)); 479 480 // Function object with non-static opCall can only be called with an 481 // instance, not with merely the type. 482 static assert(!is(typeof(naryFun!FuncObj))); 483 } 484 485 486 /++ 487 N-ary predicate that reverses the order of arguments, e.g., given 488 `pred(a, b, c)`, returns `pred(c, b, a)`. 489 +/ 490 template reverseArgs(alias fun) 491 { 492 import std.meta: Reverse; 493 /// 494 @optmath auto ref reverseArgs(Args...)(auto ref Args args) 495 if (is(typeof(fun(Reverse!args)))) 496 { 497 return fun(Reverse!args); 498 } 499 500 } 501 502 /// 503 @safe version(mir_core_test) unittest 504 { 505 int abc(int a, int b, int c) { return a * b + c; } 506 alias cba = reverseArgs!abc; 507 assert(abc(91, 17, 32) == cba(32, 17, 91)); 508 } 509 510 @safe version(mir_core_test) unittest 511 { 512 int a(int a) { return a * 2; } 513 alias _a = reverseArgs!a; 514 assert(a(2) == _a(2)); 515 } 516 517 @safe version(mir_core_test) unittest 518 { 519 int b() { return 4; } 520 alias _b = reverseArgs!b; 521 assert(b() == _b()); 522 } 523 524 @safe version(mir_core_test) unittest 525 { 526 alias gt = reverseArgs!(naryFun!("a < b")); 527 assert(gt(2, 1) && !gt(1, 1)); 528 int x = 42; 529 bool xyz(int a, int b) { return a * x < b / x; } 530 auto foo = &xyz; 531 foo(4, 5); 532 alias zyx = reverseArgs!(foo); 533 assert(zyx(5, 4) == foo(4, 5)); 534 } 535 536 /++ 537 Negates predicate `pred`. 538 +/ 539 template not(alias pred) 540 { 541 static if (!is(typeof(pred) : string) && !needOpCallAlias!pred) 542 /// 543 @optmath bool not(T...)(auto ref T args) 544 { 545 return !pred(args); 546 } 547 else 548 alias not = .not!(naryFun!pred); 549 } 550 551 /// 552 @safe version(mir_core_test) unittest 553 { 554 import std.algorithm.searching : find; 555 import std.uni : isWhite; 556 string a = " Hello, world!"; 557 assert(find!(not!isWhite)(a) == "Hello, world!"); 558 } 559 560 @safe version(mir_core_test) unittest 561 { 562 assert(not!"a != 5"(5)); 563 assert(not!"a != b"(5, 5)); 564 565 assert(not!(() => false)()); 566 assert(not!(a => a != 5)(5)); 567 assert(not!((a, b) => a != b)(5, 5)); 568 assert(not!((a, b, c) => a * b * c != 125 )(5, 5, 5)); 569 } 570 571 private template _pipe(size_t n) 572 { 573 static if (n) 574 { 575 enum i = n - 1; 576 enum _pipe = "f[" ~ i.stringof ~ "](" ~ ._pipe!i ~ ")"; 577 } 578 else 579 enum _pipe = "args"; 580 } 581 582 private template _unpipe(alias fun) 583 { 584 import std.traits: TemplateArgsOf, TemplateOf; 585 static if (__traits(compiles, TemplateOf!fun)) 586 static if (__traits(isSame, TemplateOf!fun, .pipe)) 587 alias _unpipe = TemplateArgsOf!fun; 588 else 589 alias _unpipe = fun; 590 else 591 alias _unpipe = fun; 592 593 } 594 595 private enum _needNary(alias fun) = is(typeof(fun) : string) || needOpCallAlias!fun; 596 597 /++ 598 Composes passed-in functions `fun[0], fun[1], ...` returning a 599 function `f(x)` that in turn returns 600 `...(fun[1](fun[0](x)))...`. Each function can be a regular 601 functions, a delegate, a lambda, or a string. 602 +/ 603 template pipe(fun...) 604 { 605 static if (fun.length != 1) 606 { 607 import std.meta: staticMap, Filter; 608 alias f = staticMap!(_unpipe, fun); 609 static if (f.length == fun.length && Filter!(_needNary, f).length == 0) 610 { 611 /// 612 @optmath auto ref pipe(Args...)(auto ref Args args) 613 { 614 return mixin (_pipe!(fun.length)); 615 } 616 } 617 else alias pipe = .pipe!(staticMap!(naryFun, f)); 618 } 619 else alias pipe = naryFun!(fun[0]); 620 } 621 622 /// 623 @safe version(mir_core_test) unittest 624 { 625 assert(pipe!("a + b", a => a * 10)(2, 3) == 50); 626 } 627 628 /// `pipe` can return by reference. 629 version(mir_core_test) unittest 630 { 631 int a; 632 assert(&pipe!("a", "a")(a) == &a); 633 } 634 635 /// Template bloat reduction 636 version(mir_core_test) unittest 637 { 638 enum a = "a * 2"; 639 alias b = e => e + 2; 640 641 alias p0 = pipe!(pipe!(a, b), pipe!(b, a)); 642 alias p1 = pipe!(a, b, b, a); 643 644 static assert(__traits(isSame, p0, p1)); 645 } 646 647 @safe version(mir_core_test) unittest 648 { 649 import std.algorithm.comparison : equal; 650 import std.algorithm.iteration : map; 651 import std.array : split; 652 import std.conv : to; 653 654 // First split a string in whitespace-separated tokens and then 655 // convert each token into an integer 656 assert(pipe!(split, map!(to!(int)))("1 2 3").equal([1, 2, 3])); 657 } 658 659 660 struct AliasCall(T, string methodName, TemplateArgs...) 661 { 662 T __this; 663 alias __this this; 664 665 /// 666 auto lightConst()() const @property 667 { 668 import mir.qualifier; 669 return AliasCall!(LightConstOf!T, methodName, TemplateArgs)(__this.lightConst); 670 } 671 672 /// 673 auto lightImmutable()() immutable @property 674 { 675 import mir.qualifier; 676 return AliasCall!(LightImmutableOf!T, methodName, TemplateArgs)(__this.lightImmutable); 677 } 678 679 this()(auto ref T value) 680 { 681 __this = value; 682 } 683 auto ref opCall(Args...)(auto ref Args args) 684 { 685 import std.traits: TemplateArgsOf; 686 mixin("return __this." ~ methodName ~ (TemplateArgs.length ? "!TemplateArgs" : "") ~ "(forward!args);"); 687 } 688 } 689 690 /++ 691 Replaces call operator (`opCall`) for the value using its method. 692 The funciton is designed to use with $(NDSLICE, topology, vmap) or $(NDSLICE, topology, map). 693 Params: 694 methodName = name of the methods to use for opCall and opIndex 695 TemplateArgs = template arguments 696 +/ 697 template aliasCall(string methodName, TemplateArgs...) 698 { 699 /++ 700 Params: 701 value = the value to wrap 702 Returns: 703 wrapped value with implemented opCall and opIndex methods 704 +/ 705 AliasCall!(T, methodName, TemplateArgs) aliasCall(T)(T value) @property 706 { 707 return typeof(return)(value); 708 } 709 710 /// ditto 711 ref AliasCall!(T, methodName, TemplateArgs) aliasCall(T)(return ref T value) @property @trusted 712 { 713 return *cast(typeof(return)*) &value; 714 } 715 } 716 717 /// 718 @safe pure nothrow version(mir_core_test) unittest 719 { 720 static struct S 721 { 722 auto lightConst()() const @property { return S(); } 723 724 auto fun(size_t ct_param = 1)(size_t rt_param) const 725 { 726 return rt_param + ct_param; 727 } 728 } 729 730 S s; 731 732 auto sfun = aliasCall!"fun"(s); 733 assert(sfun(3) == 4); 734 735 auto sfun10 = aliasCall!("fun", 10)(s); // uses fun!10 736 assert(sfun10(3) == 13); 737 } 738 739 /++ 740 +/ 741 template recurseTemplatePipe(alias Template, size_t N, Args...) 742 { 743 static if (N == 0) 744 alias recurseTemplatePipe = Args; 745 else 746 { 747 alias recurseTemplatePipe = Template!(.recurseTemplatePipe!(Template, N - 1, Args)); 748 } 749 } 750 751 /// 752 @safe version(mir_core_test) unittest 753 { 754 // import mir.ndslice.topology: map; 755 alias map(alias fun) = a => a; // some template 756 static assert (__traits(isSame, recurseTemplatePipe!(map, 2, "a * 2"), map!(map!"a * 2"))); 757 } 758 759 /++ 760 +/ 761 template selfAndRecurseTemplatePipe(alias Template, size_t N, Args...) 762 { 763 static if (N == 0) 764 alias selfAndRecurseTemplatePipe = Args; 765 else 766 { 767 alias selfAndRecurseTemplatePipe = Template!(.selfAndRecurseTemplatePipe!(Template, N - 1, Args)); 768 } 769 } 770 771 /// 772 @safe version(mir_core_test) unittest 773 { 774 // import mir.ndslice.topology: map; 775 alias map(alias fun) = a => a; // some template 776 static assert (__traits(isSame, selfAndRecurseTemplatePipe!(map, 2, "a * 2"), map!(pipe!("a * 2", map!"a * 2")))); 777 } 778 779 /++ 780 +/ 781 template selfTemplatePipe(alias Template, size_t N, Args...) 782 { 783 static if (N == 0) 784 alias selfTemplatePipe = Args; 785 else 786 { 787 alias selfTemplatePipe = Template!(.selfTemplatePipe!(Template, N - 1, Args)); 788 } 789 } 790 791 /// 792 @safe version(mir_core_test) unittest 793 { 794 // import mir.ndslice.topology: map; 795 alias map(alias fun) = a => a; // some template 796 static assert (__traits(isSame, selfTemplatePipe!(map, 2, "a * 2"), map!(pipe!("a * 2", map!"a * 2")))); 797 }