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 }