Thread overview
Getting resulting/return type from operation in templates
Jul 06, 2021
Rekel
Jul 06, 2021
Paul Backus
Jul 06, 2021
Rekel
Jul 06, 2021
Paul Backus
Jul 06, 2021
Rekel
July 06, 2021

I recently found __traits(compiles, ...) can be used in template constraints.
(I first used is(mixin("T.init"~op~"T2.init")) but this cause problems related to nanF)

However I'm wondering if there is an easier way to do what I'm currently doing, especially since using typeof inside the result declaration is quite cumbersome. This is a bigger problem in my less-simplified code (using matrices), where it starts getting slightly unreadable.
It also assumes the presence of .vec[0] (not necessarily a problem here but it seems like a codesmell)

Simplified:

template supported(A, string op, B) {
	const bool supported = __traits(compiles, (A a, B b) {
		mixin("return a" ~ op ~ "b;");
	});
}

struct Vec(T, uint L) {
	T[L] vec;
	alias vec this;

	auto opBinary(string op, R:
			T2[], T2)(R right) const if (supported!(T, op, T2)) {
		Vec!(typeof(mixin("this[0]" ~ op ~ "right[0]")), L) result;
		static foreach (i; 0 .. L) {
			mixin("result[i] = this[i] " ~ op ~ " right[i];");
		}
		return result;
	}

	auto opBinary(string op, R)(R right) const if (supported!(T, op, R)) {
		Vec!(typeof(mixin("this[0]" ~ op ~ "right")), L) result;
		static foreach (i; 0 .. L) {
			mixin("result[i] = this[i] " ~ op ~ " right;");
		}
		return result;
	}
}

void main(string[] args) {
	Vec!(float, 4) v = Vec!(float, 4)([0, 1, 2, 3]);
	auto w = v * 2.0;
	auto x = v + v;
	auto y = v + [1, 1, 1, 1];
	pragma(msg, typeof(w)); // prints "Vec!(double, 4u)"
	pragma(msg, typeof(x)); // prints "Vec!(float, 4u)"
	pragma(msg, typeof(y)); // prints "Vec!(float, 4u)"
}
July 06, 2021

On Tuesday, 6 July 2021 at 14:12:48 UTC, Rekel wrote:

>

I recently found __traits(compiles, ...) can be used in template constraints.
(I first used is(mixin("T.init"~op~"T2.init")) but this cause problems related to nanF)

However I'm wondering if there is an easier way to do what I'm currently doing, especially since using typeof inside the result declaration is quite cumbersome. This is a bigger problem in my less-simplified code (using matrices), where it starts getting slightly unreadable.
It also assumes the presence of .vec[0] (not necessarily a problem here but it seems like a codesmell)

Simplified:

template supported(A, string op, B) {
	const bool supported = __traits(compiles, (A a, B b) {
		mixin("return a" ~ op ~ "b;");
	});
}

Instead of having the template evaluate to a bool, have it evaluate to the type of the result:

alias ResultType(Lhs, string op, Rhs) =
    typeof(((Lhs lhs, Rhs rhs) => mixin("lhs", op, "rhs"))());

static assert(is(ResultType!(int, "+", double) == double));
static assert(is(ResultType!(string, "~", string) == string));
static assert(!is(ResultType!(string, "+", int))); // no valid result type

Then you can use is() in the template constraints to check whether the operation is supported:

auto opBinary(string op, Rhs: T2[], T2) const
    if (is(ResultType!(T, op, T2)))
{
    Vec!(ResultType!(T, op, T2), L) result;
    // etc.
}
July 06, 2021

On Tuesday, 6 July 2021 at 14:27:35 UTC, Paul Backus wrote:

>

Instead of having the template evaluate to a bool, have it evaluate to the type of the result:

alias ResultType(Lhs, string op, Rhs) =
    typeof(((Lhs lhs, Rhs rhs) => mixin("lhs", op, "rhs"))());

static assert(is(ResultType!(int, "+", double) == double));
static assert(is(ResultType!(string, "~", string) == string));
static assert(!is(ResultType!(string, "+", int))); // no valid result type

Then you can use is() in the template constraints to check whether the operation is supported:

auto opBinary(string op, Rhs: T2[], T2) const
    if (is(ResultType!(T, op, T2)))
{
    Vec!(ResultType!(T, op, T2), L) result;
    // etc.
}

Oh that's neat, thanks.
I guess that would make it:

auto opBinary(string op, R)(R right) const if (is(ResultType!(T, op, R))) {
	Vec!(ResultType!(T, op, R), L) result;
	static foreach (i; 0 .. L) {
		mixin("result[i] = this[i] " ~ op ~ " right;");
	}
	return result;
}

I guess I'll just have to take the result definition for what it is. At least the return type can use auto, haha. Imagine using ResultType!(T, op, R) thrice...

Is there any reason the function call in the alias is acceptable? I would imagine the () part would actually require 2 parameters.

July 06, 2021

On Tuesday, 6 July 2021 at 14:43:26 UTC, Rekel wrote:

>

Is there any reason the function call in the alias is acceptable? I would imagine the () part would actually require 2 parameters.

You're right, it does; that was a mistake on my part. It should work with Lhs.init and Rhs.init as arguments.

July 06, 2021

On Tuesday, 6 July 2021 at 15:00:24 UTC, Paul Backus wrote:

>

On Tuesday, 6 July 2021 at 14:43:26 UTC, Rekel wrote:

>

Is there any reason the function call in the alias is acceptable? I would imagine the () part would actually require 2 parameters.

You're right, it does; that was a mistake on my part. It should work with Lhs.init and Rhs.init as arguments.

Hmm . . . doing that ironically brings me back to using:

alias ResultType(Lhs, string op, Rhs) = typeof(mixin("Lhs.init" ~ op ~ "Rhs.init"));

auto opBinary(string op, R:
		T2[], T2)(R right) const if (is(ResultType!(T, op, T2))) {
	Vec!(ResultType!(T, op, T2), L) result;
	static foreach (i; 0 .. L) {
		mixin("result[i] = this[i] " ~ op ~ " right[i];");
	}
	return result;
}

Kind of ironic this is basically an alias of my old solution.
Strangely enough the nanF issue went away though, I guess I misinterpreted this.
Thanks for your help anyway 😅