1 module vsignal.utils; 2 3 /** 4 Attempts to forward each parameter. If a parameters cannot be forwarded to F 5 then the same is treated as an 'lvalue' and copied. 6 7 Params: 8 F = Function to follow. 9 args = a parameter list or an std.meta.AliasSeq. 10 11 Returns: An `AliasSeq` of args ready to be correctly forwarded or copied. 12 */ 13 package template tryForward(alias F, args...) 14 { 15 import core.lifetime : forward; 16 import std.meta : AliasSeq; 17 18 // binary search forwardable params 19 template tryForwardImpl(size_t l, size_t r) 20 { 21 alias fwd = forward!(args[l..r]); 22 23 // Safe clause 24 static if (l == r) 25 alias tryForwardImpl = AliasSeq!(); 26 27 // If it compiles then F(..., (l .. r), ...) can be forwarded 28 else static if (__traits(compiles, F(args[0..l], fwd, args[r..$]))) 29 alias tryForwardImpl = fwd; 30 31 // If we're down to 2 elems just recurse each 32 else static if (r - l == 2) 33 alias tryForwardImpl = AliasSeq!(tryForwardImpl!(l, r - 1), tryForwardImpl!(r - 1, r)); 34 35 // If it didn't compile and we have 1 elem left then treat as `lvalue` 36 else static if (r - l == 1) 37 alias tryForwardImpl = args[l..r]; 38 39 // If all else fails divide in half 40 else 41 alias tryForwardImpl = AliasSeq!(tryForwardImpl!(l, r / 2), tryForwardImpl!(r / 2, r)); 42 } 43 44 alias tryForward = tryForwardImpl!(0, args.length); 45 } 46 47 @safe pure nothrow @nogc unittest 48 { 49 class C 50 { 51 static int foo(int) { return 1; } 52 static int foo(ref int) { return 2; } 53 54 static int foo(int, ref int) { return 1; } 55 static int foo(ref int, int) { return 2; } 56 57 static int foo(ref int, int, const int, const ref int) { return 2; } 58 } 59 60 int foo(Args...)(auto ref Args args) 61 { 62 return C.foo(tryForward!(C.foo, args)); 63 } 64 65 int i; 66 67 assert(foo(4) == 1); 68 assert(foo(i) == 2); 69 70 assert(foo(4, i) == 1); 71 assert(foo(i, 4) == 2); 72 73 assert(foo(i, 4, i, 4) == 2); 74 75 static assert(!__traits(compiles, foo(i, i))); 76 static assert(!__traits(compiles, foo(4, 4))); 77 } 78 79 /** 80 Helper for inline imports. 81 82 Params: 83 mod = module to import. 84 */ 85 package template from(string mod) 86 { 87 mixin("import from = ", mod, ";"); 88 } 89 90 /** 91 Correctly calls a function with the given arguments. 92 93 If `F` is not a Callable (e.g lambda infered as void) then `F` is called with 94 `args`. If `F` is Callable and is a member function of `args[0]`, then the 95 function is called using `args[0]` as the instance with `args[1..$]` as the 96 parameters. Otherwise, `F` is called normally. The arguments are attempted to be 97 forwarded with `tryForward`. 98 99 Params: 100 F = the function to invoke. 101 args = the arguments to call F with. 102 103 Examples: 104 --- 105 void method(ref Foo) {} 106 107 struct Foo { void method() {} } 108 Foo foo; 109 110 invoke!(Foo.method)(foo); // correctly invokes method from foo 111 invoke!method(foo); // correctly invokes method 112 invoke!(_ => _ + 1)(4); // correctly invokes the lambda 113 --- 114 115 Returns: The value returned by `F`. 116 */ 117 package auto ref invoke(alias F, Args...)(auto ref Args args) 118 if (!from!"std.traits".isCallable!F) 119 { 120 // we enter here because F might be a lambda 121 // if F is a lambda we know it is not a data member 122 return F(tryForward!(F, args)); 123 } 124 125 /// 126 package auto ref invoke(alias F, Args...)(auto ref Args args) 127 if (from!"std.traits".isCallable!F) 128 { 129 static if (!Args.length) 130 return F(); 131 132 else 133 { 134 alias front = args[0]; 135 alias FrontType = Args[0]; 136 alias params = args[1..$]; 137 138 static if (is(__traits(parent, F) == FrontType)) 139 { 140 return __traits(child, front, F)(tryForward!(F, params)); 141 } 142 else 143 { 144 static immutable Tinit = FrontType.init; 145 return F(front, tryForward!(F, Tinit, params)[1..$]); 146 } 147 } 148 }