On Fri, Jan 13, 2023 at 02:22:34PM +0000, Sergei Nosov via Digitalmars-d-learn wrote: > Hey, everyone! > > I was wondering if there's a strong reason behind not implementing > elementwise operations on tuples? > > Say, I've decided to store 2d points in a `Tuple!(int, int)`. It would > be convenient to just write `a + b` to yield another `Tuple!(int, > int)`.
I've written a Vec type that implements precisely this, using tuples behind the scenes as the implementation, and operator overloading to allow nice syntax for vector arithmetic. -----------------------------------snip------------------------------------ /** * Represents an n-dimensional vector of values. */ struct Vec(T, size_t n) { T[n] impl; alias impl this; /** * Per-element unary operations. */ Vec opUnary(string op)() if (is(typeof((T t) => mixin(op ~ "t")))) { Vec result; foreach (i, ref x; result.impl) x = mixin(op ~ "this[i]"); return result; } /** * Per-element binary operations. */ Vec opBinary(string op, U)(Vec!(U,n) v) if (is(typeof(mixin("T.init" ~ op ~ "U.init")))) { Vec result; foreach (i, ref x; result.impl) x = mixin("this[i]" ~ op ~ "v[i]"); return result; } /// ditto Vec opBinary(string op, U)(U y) if (isScalar!U && is(typeof(mixin("T.init" ~ op ~ "U.init")))) { Vec result; foreach (i, ref x; result.impl) x = mixin("this[i]" ~ op ~ "y"); return result; } /// ditto Vec opBinaryRight(string op, U)(U y) if (isScalar!U && is(typeof(mixin("U.init" ~ op ~ "T.init")))) { Vec result; foreach (i, ref x; result.impl) x = mixin("y" ~ op ~ "this[i]"); return result; } /** * Per-element assignment operators. */ void opOpAssign(string op, U)(Vec!(U,n) v) if (is(typeof({ T t; mixin("t " ~ op ~ "= U.init;"); }))) { foreach (i, ref x; impl) mixin("x " ~ op ~ "= v[i];"); } void toString(W)(W sink) const if (isOutputRange!(W, char)) { import std.format : formattedWrite; formattedWrite(sink, "(%-(%s,%))", impl[]); } } /** * Convenience function for creating vectors. * Returns: Vec!(U,n) instance where n = args.length, and U is the common type * of the elements given in args. A compile-time error results if the arguments * have no common type. */ auto vec(T...)(T args) { static if (args.length == 1 && is(T[0] == U[n], U, size_t n)) return Vec!(U, n)(args); else static if (is(typeof([args]) : U[], U)) return Vec!(U, args.length)([ args ]); else static assert(false, "No common type for " ~ T.stringof); } /// unittest { // Basic vector construction auto v1 = vec(1,2,3); static assert(is(typeof(v1) == Vec!(int,3))); assert(v1[0] == 1 && v1[1] == 2 && v1[2] == 3); // Vector comparison auto v2 = vec(1,2,3); assert(v1 == v2); // Unary operations assert(-v1 == vec(-1, -2, -3)); assert(++v2 == vec(2,3,4)); assert(v2 == vec(2,3,4)); assert(v2-- == vec(2,3,4)); assert(v2 == vec(1,2,3)); // Binary vector operations auto v3 = vec(2,3,1); assert(v1 + v3 == vec(3,5,4)); auto v4 = vec(1.1, 2.2, 3.3); static assert(is(typeof(v4) == Vec!(double,3))); assert(v4 + v1 == vec(2.1, 4.2, 6.3)); // Binary operations with scalars assert(vec(1,2,3)*2 == vec(2,4,6)); assert(vec(4,2,6)/2 == vec(2,1,3)); assert(3*vec(1,2,3) == vec(3,6,9)); // Non-numeric vectors auto sv1 = vec("a", "b"); static assert(is(typeof(sv1) == Vec!(string,2))); assert(sv1 ~ vec("c", "d") == vec("ac", "bd")); assert(sv1 ~ "post" == vec("apost", "bpost")); assert("pre" ~ sv1 == vec("prea", "preb")); } unittest { // Test opOpAssign. auto v = vec(1,2,3); auto w = vec(4,5,6); v += w; assert(v == vec(5,7,9)); } unittest { int[4] z = [ 1, 2, 3, 4 ]; auto v = vec(z); static assert(is(typeof(v) == Vec!(int,4))); assert(v == vec(1, 2, 3, 4)); } unittest { import std.format : format; auto v = vec(1,2,3,4); assert(format("%s", v) == "(1,2,3,4)"); } -----------------------------------snip------------------------------------ T -- Never ascribe to malice that which is adequately explained by incompetence. -- Napoleon Bonaparte