digitalmars.D - opConcatAll?
- Steven Schveighoffer (29/29) Jul 02 2020 If I have an array:
- jmh530 (7/12) Jul 02 2020 With a few small changes, you could just as easily have written
- Steven Schveighoffer (3/21) Jul 02 2020 Hm.. I think you misunderstand the example.
- jmh530 (15/26) Jul 02 2020 I think I understand the example, but I may not have made my
- Steven Schveighoffer (6/35) Jul 02 2020 OK, I see what you are saying. And yes, a generalized mechanism to
- Nick Treleaven (11/14) Jul 02 2020 Sounds good, although I think multiple other operations beside
- Steven Schveighoffer (3/23) Jul 02 2020 That would be awesome, and cover my case!
- Petar Kirov [ZombineDev] (10/34) Jul 03 2020 We kind of have this already, but a bit closer to what jmh was
- Andrei Alexandrescu (2/40) Jul 03 2020 How are parens handled?
- Aliak (3/25) Jul 03 2020 By multiple consecutive lowerings in order of parens?
- Steven Schveighoffer (8/47) Jul 03 2020 There are possibilities. The array vector operations seem to be passed
- Petar Kirov [ZombineDev] (8/23) Jul 03 2020 Parens don't need special handling since they're already handled
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (15/31) Jul 02 2020 One typical case that this would do great for is BigInt modular
- Timon Gehr (5/25) Jul 03 2020 Maybe it could pass a single fully parenthesized expression instead of
- user1234 (3/19) Jul 03 2020 Wow, that looked cool the first 3 secons but now I wander if you
- Steven Schveighoffer (6/30) Jul 03 2020 Meh, the user can do anything he wants already. One can get around
- jmh530 (7/13) Jul 03 2020 I see no reason why Aliak's suggestion above with respect to
- Steven Schveighoffer (10/24) Jul 03 2020 That's what's already done today.
- Ben Jones (11/20) Jul 03 2020 I think grouping all operations with the same precedence using
- Timon Gehr (3/7) Jul 03 2020 Walter's stance on expression templates is that they are operator
- user1234 (4/15) Jul 03 2020 Your suggestion has no issue with precedence, it's only the
- user1234 (4/7) Jul 03 2020 What I'll say is off topic but what I wanted once is
- =?UTF-8?Q?Ali_=c3=87ehreli?= (15/18) Jul 03 2020 Given the confusions raised elsewhere in this thread, I think that
- Steven Schveighoffer (17/41) Jul 03 2020 Any non-commutative operations rely on order of operations. Division,
- Nick Treleaven (40/48) Jul 04 2020 What if the compiler calls opBinaryTemp when it detects an
- Nick Treleaven (54/61) Jul 05 2020 The lowering actually compiles now:
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (15/24) Jul 03 2020 That already has a defined behavior:
If I have an array: int[] arr = [1, 2, 3]; And I use it in a concatenation chain: auto newArr = [0] ~ arr ~ [4, 5, 6]; The compiler calls a single function to allocate these together (_d_arraycatnTX). However, if I define a struct instead: struct S { int [] x; S opBinary(string s : "~")(S other) {return S(x ~ other.x); } } Now, if I do: S arr = S([1, 2, 3]); auto newArr = S([0]) ~ arr ~ S([4, 5, 6]); I get one call PER operator, in other words, it gets translated to: S([0])opBinary!"~"(arr).opBinary!"~"(S([4, 5, 6])); Which means one separate allocation for each field. If you have a lot of these all put together, it could add up to a lot of allocations, with most of the allocations as temporaries. What about an opConcatAll (or other possible name), which can accept all the following concatenations as parameters? in other words, given: a ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d) if possible. Would that make sense? -Steve
Jul 02 2020
On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:If I have an array: int[] arr = [1, 2, 3]; And I use it in a concatenation chain: auto newArr = [0] ~ arr ~ [4, 5, 6]; [snip]With a few small changes, you could just as easily have written int[] newArr; newArr[] = [0, 0, 0] + arr[] + [4, 5, 6]; which is similar to the traditional type of problem that expression templates were designed to solve.
Jul 02 2020
On 7/2/20 12:20 PM, jmh530 wrote:On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:Hm.. I think you misunderstand the example. -SteveIf I have an array: int[] arr = [1, 2, 3]; And I use it in a concatenation chain: auto newArr = [0] ~ arr ~ [4, 5, 6]; [snip]With a few small changes, you could just as easily have written int[] newArr; newArr[] = [0, 0, 0] + arr[] + [4, 5, 6]; which is similar to the traditional type of problem that expression templates were designed to solve.
Jul 02 2020
On Thursday, 2 July 2020 at 18:40:21 UTC, Steven Schveighoffer wrote:I think I understand the example, but I may not have made my point clear enough... You are concerned about a ~ b ~ c and avoiding unnecessary allocations. Expression templates are concerned with problems like a + b + c and avoiding the use of unnecessary temporaries. They seem like similar problems to me. Temporaries and allocations are obviously different, but the structure of the problem is similar. You yourself note that "most of the allocations as temporaries". If my point makes sense, then the implication is that this is a problem that is not unique to concatenation and also applies to the mathematical operators, in their own way.[snip] With a few small changes, you could just as easily have written int[] newArr; newArr[] = [0, 0, 0] + arr[] + [4, 5, 6]; which is similar to the traditional type of problem that expression templates were designed to solve.Hm.. I think you misunderstand the example. -Steve
Jul 02 2020
On 7/2/20 3:18 PM, jmh530 wrote:On Thursday, 2 July 2020 at 18:40:21 UTC, Steven Schveighoffer wrote:OK, I see what you are saying. And yes, a generalized mechanism to handle this would be useful for both avoiding allocations and avoiding temporaries (among other things). Nick's idea is pretty good for both. -SteveI think I understand the example, but I may not have made my point clear enough... You are concerned about a ~ b ~ c and avoiding unnecessary allocations. Expression templates are concerned with problems like a + b + c and avoiding the use of unnecessary temporaries. They seem like similar problems to me. Temporaries and allocations are obviously different, but the structure of the problem is similar. You yourself note that "most of the allocations as temporaries". If my point makes sense, then the implication is that this is a problem that is not unique to concatenation and also applies to the mathematical operators, in their own way.[snip] With a few small changes, you could just as easily have written int[] newArr; newArr[] = [0, 0, 0] + arr[] + [4, 5, 6]; which is similar to the traditional type of problem that expression templates were designed to solve.Hm.. I think you misunderstand the example.
Jul 02 2020
On Thursday, 2 July 2020 at 20:29:59 UTC, Steven Schveighoffer wrote:[snip] OK, I see what you are saying. And yes, a generalized mechanism to handle this would be useful for both avoiding allocations and avoiding temporaries (among other things). Nick's idea is pretty good for both. -SteveNick's idea only handles the case where all the operators are the same, e.g. a + b + c and not a + b - c.
Jul 02 2020
On Thursday, 2 July 2020 at 20:33:47 UTC, jmh530 wrote:[snip] Nick's idea only handles the case where all the operators are the same, e.g. a + b + c and not a + b - c.Oh, sorry, I didn't see that last bit. Nevermind.
Jul 02 2020
On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:a ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 02 2020
On 7/2/20 3:28 PM, Nick Treleaven wrote:On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:That would be awesome, and cover my case! -Stevea ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 02 2020
On Thursday, 2 July 2020 at 20:26:15 UTC, Steven Schveighoffer wrote:On 7/2/20 3:28 PM, Nick Treleaven wrote:We kind of have this already, but a bit closer to what jmh was describing earlier. Though it's just an implementation detail inside druntime for implementing array ops: https://github.com/dlang/druntime/blob/v2.092.1/src/core/internal/array/operations.d#L15-L36 I think that it would be pretty cool if this technique could be applied to user-defined types (e.g. allow library authors to provide their own `arrayOp` implementation of their types).On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:That would be awesome, and cover my case! -Stevea ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 03 2020
On 7/3/20 3:17 AM, Petar Kirov [ZombineDev] wrote:On Thursday, 2 July 2020 at 20:26:15 UTC, Steven Schveighoffer wrote:How are parens handled?On 7/2/20 3:28 PM, Nick Treleaven wrote:We kind of have this already, but a bit closer to what jmh was describing earlier. Though it's just an implementation detail inside druntime for implementing array ops: https://github.com/dlang/druntime/blob/v2.092.1/src/core/internal/array/ perations.d#L15-L36 I think that it would be pretty cool if this technique could be applied to user-defined types (e.g. allow library authors to provide their own `arrayOp` implementation of their types).On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:That would be awesome, and cover my case! -Stevea ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 03 2020
On Friday, 3 July 2020 at 15:39:08 UTC, Andrei Alexandrescu wrote:On 7/3/20 3:17 AM, Petar Kirov [ZombineDev] wrote:By multiple consecutive lowerings in order of parens? (a op b) op c => t.op(a, b).op(c)On Thursday, 2 July 2020 at 20:26:15 UTC, Steven Schveighoffer wrote:How are parens handled?On 7/2/20 3:28 PM, Nick Treleaven wrote:We kind of have this already, but a bit closer to what jmh was describing earlier. Though it's just an implementation detail inside druntime for implementing array ops: https://github.com/dlang/druntime/blob/v2.092.1/src/core/internal/array/operations.d#L15-L36 I think that it would be pretty cool if this technique could be applied to user-defined types (e.g. allow library authors to provide their own `arrayOp` implementation of their types).[...]That would be awesome, and cover my case! -Steve
Jul 03 2020
On 7/3/20 11:39 AM, Andrei Alexandrescu wrote:On 7/3/20 3:17 AM, Petar Kirov [ZombineDev] wrote:There are possibilities. The array vector operations seem to be passed in RPN, negating the need for parentheses handling (maybe the compiler translates the expression for you?) Another possibility is just to stop at parentheses boundaries. e.g.: (a + b + c) * d => a.opNary!(["+", "+"])(b, c).opBinary!"*"(d); Which isn't as powerful, but still helpful in many cases. -SteveOn Thursday, 2 July 2020 at 20:26:15 UTC, Steven Schveighoffer wrote:How are parens handled?On 7/2/20 3:28 PM, Nick Treleaven wrote:We kind of have this already, but a bit closer to what jmh was describing earlier. Though it's just an implementation detail inside druntime for implementing array ops: https://github.com/dlang/druntime/blob/v2.092.1/src/core/internal/array/ perations.d#L15-L36 I think that it would be pretty cool if this technique could be applied to user-defined types (e.g. allow library authors to provide their own `arrayOp` implementation of their types).On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:That would be awesome, and cover my case!a ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 03 2020
On Friday, 3 July 2020 at 15:39:08 UTC, Andrei Alexandrescu wrote:On 7/3/20 3:17 AM, Petar Kirov [ZombineDev] wrote:Parens don't need special handling since they're already handled by the dmd parser. The frontend simply traverses the expression tree for the array op and outputs it in reverse polish notation here: https://github.com/dlang/dmd/blob/v2.092.1/src/dmd/arrayop.d#L180-L255 For reference, here's the PR by Martin Nowak, who implemented this: https://github.com/dlang/dmd/pull/7032[..] We kind of have this already, but a bit closer to what jmh was describing earlier. Though it's just an implementation detail inside druntime for implementing array ops: https://github.com/dlang/druntime/blob/v2.092.1/src/core/internal/array/operations.d#L15-L36 I think that it would be pretty cool if this technique could be applied to user-defined types (e.g. allow library authors to provide their own `arrayOp` implementation of their types).How are parens handled?
Jul 03 2020
On Thursday, 2 July 2020 at 19:28:15 UTC, Nick Treleaven wrote:On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:One typical case that this would do great for is BigInt modular exponentiation (a ^^ b) % c. One problem with this idea is with order of operations - there's no way to distinguish (a*b)+c from a*(b+c). I wrote[0] a suggestion two years ago for something I called rvalue types, which might be interesting for the use cases described here. Essentially, it's a type for a temporary value that will decay to a regular value whenever it's assigned to something - somewhat like an alias this that is preferred to using the actual type. -- Simen [0]: https://forum.dlang.org/post/sfvqxnnibgbuebbxweqb forum.dlang.orga ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 02 2020
On 02.07.20 21:28, Nick Treleaven wrote:On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:Maybe it could pass a single fully parenthesized expression instead of an array of operators. But given D's philosophy of crippling operator overloading in the name of prevention of "abuse", something like this will likely never be added.a ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 03 2020
On Thursday, 2 July 2020 at 19:28:15 UTC, Nick Treleaven wrote:On Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:Wow, that looked cool the first 3 secons but now I wander if you really want to let the user break the rules of precedence ?a ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 03 2020
On 7/3/20 11:25 AM, user1234 wrote:On Thursday, 2 July 2020 at 19:28:15 UTC, Nick Treleaven wrote:Meh, the user can do anything he wants already. One can get around precedence by deferring calls. It is true, though, that I didn't think of precedence when I first posted -- I just wanted to avoid some allocations. -SteveOn Thursday, 2 July 2020 at 15:47:39 UTC, Steven Schveighoffer wrote:Wow, that looked cool the first 3 secons but now I wander if you really want to let the user break the rules of precedence ?a ~ b ~ c ~ d will translate to a.opConcatAll(b, c, d)Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args) E.g. perhaps BigInt can avoid one or more reallocations for >2 args. The compiler would try opNary first in that case, and fallback to successive calls to opBinary as now. In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)
Jul 03 2020
On Friday, 3 July 2020 at 17:13:32 UTC, Steven Schveighoffer wrote:[snip] Meh, the user can do anything he wants already. One can get around precedence by deferring calls. It is true, though, that I didn't think of precedence when I first posted -- I just wanted to avoid some allocations. -SteveI see no reason why Aliak's suggestion above with respect to parentheses cannot be also used to ensure precedence is followed. For instance, if you have a + b * c, then it gets re-written to a + (b * c), which gets re-written to something like a.op1(b.op2(c)).
Jul 03 2020
On 7/3/20 1:54 PM, jmh530 wrote:On Friday, 3 July 2020 at 17:13:32 UTC, Steven Schveighoffer wrote:That's what's already done today. The idea here is to pass all the operations and parameters at once. There are actually advantages in some cases to have the entire expression, as some things could be optimized by reordering operations (in a valid way). The further question of this subthread is -- can we loosen some of the precedence rules so this provides more capabilities, or would it be crippled by enforcing precedence rules? -Steve[snip] Meh, the user can do anything he wants already. One can get around precedence by deferring calls. It is true, though, that I didn't think of precedence when I first posted -- I just wanted to avoid some allocations.I see no reason why Aliak's suggestion above with respect to parentheses cannot be also used to ensure precedence is followed. For instance, if you have a + b * c, then it gets re-written to a + (b * c), which gets re-written to something like a.op1(b.op2(c)).
Jul 03 2020
On Friday, 3 July 2020 at 18:07:08 UTC, Steven Schveighoffer wrote:That's what's already done today. The idea here is to pass all the operations and parameters at once. There are actually advantages in some cases to have the entire expression, as some things could be optimized by reordering operations (in a valid way). The further question of this subthread is -- can we loosen some of the precedence rules so this provides more capabilities, or would it be crippled by enforcing precedence rules? -SteveI think grouping all operations with the same precedence using your new nAryOp idea is a good compromise. If you need to mess with precedence rules, then you can write expression templates to do so. I imagine the most common case is string concatenation with a bunch of strings which that approach would handle. Example: a + b - c*d/e would become a.opNaray!(["+", "-"])(a, b, c.opNary!(["*", "/"])(c, d, e))
Jul 03 2020
On 03.07.20 20:39, Ben Jones wrote:I think grouping all operations with the same precedence using your new nAryOp idea is a good compromise. If you need to mess with precedence rules, then you can write expression templates to do so.Walter's stance on expression templates is that they are operator overloading abuse.
Jul 03 2020
On Friday, 3 July 2020 at 17:13:32 UTC, Steven Schveighoffer wrote:On 7/3/20 11:25 AM, user1234 wrote:Your suggestion has no issue with precedence, it's only the version proposed by Nick, as it allows to mix several operators.On Thursday, 2 July 2020 at 19:28:15 UTC, Nick Treleaven wrote:Meh, the user can do anything he wants already. One can get around precedence by deferring calls. It is true, though, that I didn't think of precedence when I first posted -- I just wanted to avoid some allocations. -Steve[...]Wow, that looked cool the first 3 secons but now I wander if you really want to let the user break the rules of precedence ?
Jul 03 2020
On Thursday, 2 July 2020 at 19:28:15 UTC, Nick Treleaven wrote:In fact, maybe some types could benefit from intercepting different operations at once: opNary(string[] ops, T...)(T args)What I'll say is off topic but what I wanted once is opDispatch(string[] members, T...)(T args) To solve a whole identifier chain ine one shot.
Jul 03 2020
On 7/2/20 12:28 PM, Nick Treleaven wrote:Sounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args)Given the confusions raised elsewhere in this thread, I think that syntax gives the biggest benefit; defined for a single operator. And we don't even need a new keyword: opBinary(string op, T...)(T args) which would solve Steve's original issue. Of course, doing the expected thing and the associativity issue would still be left to the implementer. For example, in order to stay consistent with the rest of the type system, the programmer should handle b and c before a below because ^^ is right-associative: a ^^ b ^^ c Well, we have to trust the programmer anyway. So, I propose an extension to opBinary to optionally take multiple arguments. Ali
Jul 03 2020
On 7/3/20 3:06 PM, Ali Çehreli wrote:On 7/2/20 12:28 PM, Nick Treleaven wrote: > Sounds good, although I think multiple other operations beside > concatenation could benefit from being intercepted: > > opNary(string op, T...)(T args) Given the confusions raised elsewhere in this thread, I think that syntax gives the biggest benefit; defined for a single operator. And we don't even need a new keyword: opBinary(string op, T...)(T args)Just nitpicking, but opBinary is not a keyword, so neither would opNary be.which would solve Steve's original issue. Of course, doing the expected thing and the associativity issue would still be left to the implementer. For example, in order to stay consistent with the rest of the type system, the programmer should handle b and c before a below because ^^ is right-associative: a ^^ b ^^ c Well, we have to trust the programmer anyway.Any non-commutative operations rely on order of operations. Division, subtraction, etc.So, I propose an extension to opBinary to optionally take multiple arguments.It would be kind of weird/unfortunate for this to support a + b + c, but not a + b - c. We could still do it all with opBinary, as: opBinary(string[] ops, Args...)(Args args) And either enforce precedence by reordering the operations (like Petar suggests) into RPN, or give up when precedence isn't strictly left-to-right. One can always break up the operations into subexpressions and sub-calls to opBinary/opNary. I think there are sufficient cases which could benefit from this to warrant a consideration for making a DIP. What I'm really more concerned about is pushback on "operator abuse", which clearly this could allow. I wouldn't go forward without some feedback from Walter. -Steve
Jul 03 2020
On Friday, 3 July 2020 at 20:27:10 UTC, Steven Schveighoffer wrote:It would be kind of weird/unfortunate for this to support a + b + c, but not a + b - c. We could still do it all with opBinary, as: opBinary(string[] ops, Args...)(Args args) And either enforce precedence by reordering the operations (like Petar suggests) into RPN, or give up when precedence isn't strictly left-to-right. One can always break up the operations into subexpressions and sub-calls to opBinary/opNary.What if the compiler calls opBinaryTemp when it detects an expression that would be used in another opBinary call? Given: S s1, s2, s3; `s1 ~ s2 ~ s3` would be lowered to: `s1.opBinaryTemp!"~"(s2).opBinary!"~"(s3)` Concatenation is commutative, but for other operations brackets would be handled naturally by the compiler: `s1 * (s2 + s3)` lowers to: `s1.opBinary!"*"(s2.opBinaryTemp!"+"(s3))` The following code would support either 1 concatenation, or 2 concatenations being done at once: struct S { int[] data; struct Expr { S left, right; // left ~ right ~ rhs auto opBinary(string op : "~")(S rhs) { size_t lhsLen = left.data.len + right.data.len; auto r = new int[lhsLen + rhs.data.len]; r[0..left.data.len] = left.data; r[left.data.len..$][0..right.data.len] = right.data; r[lhsLen..$][0..rhs.data.len] = rhs.data; return r; } } auto opBinaryTemp(string op : "~")(S rhs) { return Expr(this, right); } // this ~ rhs auto opBinary(string op : "~")(S rhs) { return S(data ~ rhs.data); } } Maybe this pattern could be extended to work with arbitrary-length sequences of concatenation. Perhaps it could also be extended to work with differing `op` kinds. Although Expr is essentially an expression template, its use is controlled and the above code doesn't need `alias this`.
Jul 04 2020
On Saturday, 4 July 2020 at 12:37:23 UTC, Nick Treleaven wrote:What if the compiler calls opBinaryTemp when it detects an expression that would be used in another opBinary call? Given: S s1, s2, s3; `s1 ~ s2 ~ s3` would be lowered to: `s1.opBinaryTemp!"~"(s2).opBinary!"~"(s3)`The lowering actually compiles now: https://github.com/ntrel/stuff/tree/master/optempMaybe this pattern could be extended to work with arbitrary-length sequences of concatenation.From the above link: struct S { int[] data; static struct Expr(T...) { T items; static foreach (E; T) static assert(is(E == S)); // concat(items) ~ rhs S opBinary(string op : "~")(S rhs) { auto len = rhs.data.length; foreach (s; items) len += s.data.length; auto r = new int[len]; size_t i; foreach (s; items) { r[i..$][0..s.data.length] = s.data; i += s.data.length; } r[i..$] = rhs.data; return S(r); } auto opBinaryTemp(string op : "~")(S rhs) { return expr(items, rhs); } } private static expr(T...)(T args) { return Expr!T(args); } auto opBinaryTemp(string op : "~")(S rhs) { return expr(this, rhs); } // this ~ rhs S opBinary(string op : "~")(S rhs) { return expr(this) ~ rhs; } } void main() { import std.stdio; auto s1 = S([1,10]); auto s2 = S([2,22]); auto s3 = S([3,-3]); //s1 ~ s2 s1.opBinary!"~"(s2).data.writeln; //s1 ~ s2 ~ s3 s1.opBinaryTemp!"~"(s2).opBinary!"~"(s3).data.writeln; //s1 ~ s2 ~ s3 ~ s1 s1.opBinaryTemp!"~"(s2).opBinaryTemp!"~"(s3).opBinary!"~"(s1).data.writeln; }
Jul 05 2020
On Friday, 3 July 2020 at 19:06:21 UTC, Ali Çehreli wrote:On 7/2/20 12:28 PM, Nick Treleaven wrote:That already has a defined behavior: alias AliasSeq(T...) = T; struct S { AliasSeq!(int, int, int) fields; void opBinary(string op, T...)(T args) { pragma(msg, T); // (int, int, int) } } unittest { S s; s + s.fields; } -- SimenSounds good, although I think multiple other operations beside concatenation could benefit from being intercepted: opNary(string op, T...)(T args)Given the confusions raised elsewhere in this thread, I think that syntax gives the biggest benefit; defined for a single operator. And we don't even need a new keyword: opBinary(string op, T...)(T args)
Jul 03 2020