Thread overview
Best way of checking for a templated function instantiation
Aug 10, 2016
Arafel
Aug 10, 2016
Nicholas Wilson
Aug 10, 2016
Meta
Aug 10, 2016
Meta
Aug 10, 2016
Arafel
Aug 10, 2016
Jonathan M Davis
Aug 10, 2016
Meta
Aug 10, 2016
Jonathan M Davis
August 10, 2016
Hi,

I'm trying to check at compilation time if a given type implements some operator (let's assume it's '+' in this case), without caring about the type of the parameters it accepts. Since operator overloading is expressed in D through templated functions, what is the preferred way of checking if a template is / can be instantiated with a given parameter list?

So far I've come with a solution using __trait(compiles, ...), but perhaps it's not 100% reliable -I'm no expert in template wizardry-, or there are better options. I also tried with hasMember, but it apparantly only shows that "opBinary" is indeed present, but nothing more:

---
void main() {
	struct S {
		int opBinary(string op)(int i) if (op == "+") {
			return 0;
		}
	}

	static assert(__traits(compiles, S.opBinary!"+"));
	static assert(!__traits(compiles, S.opBinary!"-"));
}
---
August 10, 2016
On Wednesday, 10 August 2016 at 12:36:14 UTC, Arafel wrote:
> Hi,
>
> I'm trying to check at compilation time if a given type implements some operator (let's assume it's '+' in this case), without caring about the type of the parameters it accepts. Since operator overloading is expressed in D through templated functions, what is the preferred way of checking if a template is / can be instantiated with a given parameter list?
>
> [...]

static assert(is(typeof(S()+42)));
static assert(!is(typeof(S()-42)));
August 10, 2016
On Wednesday, 10 August 2016 at 12:36:14 UTC, Arafel wrote:
> Hi,
>
> I'm trying to check at compilation time if a given type implements some operator (let's assume it's '+' in this case), without caring about the type of the parameters it accepts. Since operator overloading is expressed in D through templated functions, what is the preferred way of checking if a template is / can be instantiated with a given parameter list?
>
> So far I've come with a solution using __trait(compiles, ...), but perhaps it's not 100% reliable -I'm no expert in template wizardry-, or there are better options. I also tried with hasMember, but it apparantly only shows that "opBinary" is indeed present, but nothing more:
>
> ---
> void main() {
> 	struct S {
> 		int opBinary(string op)(int i) if (op == "+") {
> 			return 0;
> 		}
> 	}
>
> 	static assert(__traits(compiles, S.opBinary!"+"));
> 	static assert(!__traits(compiles, S.opBinary!"-"));
> }
> ---

__traits(compiles) is pretty much the only way to do it. For example, if you want to check that S supports opBinary!"+"(int):

static assert(__traits(compiles, auto _ = S.init.opBinary!"+"(int.init));

And if you want to check that the return type is int or implicitly converts to int, you can change the `auto _ = ...` to `int _ = ...`. However, if you want to be more explicit about it, you can do the following:

import std.traits;

static assert(is(ReturnType!(S.opBinary!"+") == int)); //Change to `... : int` for implicit conversion checking
August 10, 2016
On Wednesday, 10 August 2016 at 13:37:47 UTC, Meta wrote:
> static assert(__traits(compiles, auto _ = S.init.opBinary!"+"(int.init));

Made a typo, this should be:

static assert(__traits(compiles, { auto _ = S.init.opBinary!"+"(int.init); }));
August 10, 2016
On Wednesday, 10 August 2016 at 13:40:30 UTC, Meta wrote:
> On Wednesday, 10 August 2016 at 13:37:47 UTC, Meta wrote:
>> static assert(__traits(compiles, auto _ = S.init.opBinary!"+"(int.init));
>
> Made a typo, this should be:
>
> static assert(__traits(compiles, { auto _ = S.init.opBinary!"+"(int.init); }));

Hi!

Thanks, that would do! Just out of curiosity, would there be any way to check just that the function is defined, like what "hasMember" would do, without caring about argument number, types, etc.? Ideally something like:

__traits(hasMember, S, "opBinary!\"+\"")
August 10, 2016
On Wednesday, August 10, 2016 12:36:14 Arafel via Digitalmars-d-learn wrote:
> Hi,
>
> I'm trying to check at compilation time if a given type implements some operator (let's assume it's '+' in this case), without caring about the type of the parameters it accepts. Since operator overloading is expressed in D through templated functions, what is the preferred way of checking if a template is / can be instantiated with a given parameter list?
>
> So far I've come with a solution using __trait(compiles, ...), but perhaps it's not 100% reliable -I'm no expert in template wizardry-, or there are better options. I also tried with hasMember, but it apparantly only shows that "opBinary" is indeed present, but nothing more:
>
> ---
> void main() {
>   struct S {
>       int opBinary(string op)(int i) if (op == "+") {
>           return 0;
>       }
>   }
>
>   static assert(__traits(compiles, S.opBinary!"+"));
>   static assert(!__traits(compiles, S.opBinary!"-"));
> }
> ---

I'd advise against checking for opBinary in general, because it'll only work with user-defined types, whereas a built-in type may define the operator. It's usually better to test that the operator works rather than that opBinary exists.  So, you end up with checks like __traits(compiles, lhs + rhs) or __traits(compiles, T.init + T.init). If you're picky about the return type, you can do stuff like __traits(compiles, T t = T.init + T.init) (that might require braces around the statement along with a semicolon; I don't remember). If you test for it enough, you can create an eponymous template that wraps the test so that you just have to do something like hasAdd!T or hasBinaryOp!("+", T). That's basically what you get with traits like isInputRange. They're eponymous templates that wrap tests that use is(typeof(...)) or __traits(compiles, ...) to test that a particular block of code or expression compiles, but wrapping that in an eponymous template makes it more idiomatic and makes it so that you don't have to duplicate the implementation of the check everywhere (which is important if the check isn't simple).

- Jonathan M Davis

August 10, 2016
On Wednesday, August 10, 2016 13:57:54 Arafel via Digitalmars-d-learn wrote:
> On Wednesday, 10 August 2016 at 13:40:30 UTC, Meta wrote:
> > On Wednesday, 10 August 2016 at 13:37:47 UTC, Meta wrote:
> >> static assert(__traits(compiles, auto _ =
> >> S.init.opBinary!"+"(int.init));
> >
> > Made a typo, this should be:
> >
> > static assert(__traits(compiles, { auto _ =
> > S.init.opBinary!"+"(int.init); }));
>
> Hi!
>
> Thanks, that would do! Just out of curiosity, would there be any way to check just that the function is defined, like what "hasMember" would do, without caring about argument number, types, etc.? Ideally something like:
>
> __traits(hasMember, S, "opBinary!\"+\"")

__traits(allMembers, S) would give "opBinary", so I don't think so. You can check that the type defined an overloaded binary operator but not which one.

- Jonathan M Davis

August 10, 2016
On Wednesday, 10 August 2016 at 13:57:54 UTC, Arafel wrote:
> On Wednesday, 10 August 2016 at 13:40:30 UTC, Meta wrote:
>> On Wednesday, 10 August 2016 at 13:37:47 UTC, Meta wrote:
>>> static assert(__traits(compiles, auto _ = S.init.opBinary!"+"(int.init));
>>
>> Made a typo, this should be:
>>
>> static assert(__traits(compiles, { auto _ = S.init.opBinary!"+"(int.init); }));
>
> Hi!
>
> Thanks, that would do! Just out of curiosity, would there be any way to check just that the function is defined, like what "hasMember" would do, without caring about argument number, types, etc.? Ideally something like:
>
> __traits(hasMember, S, "opBinary!\"+\"")

Unfortunately you're stuck using __traits(compiles) as `opBinary!"+"` is not a symbol.

static assert(__traits(compiles, { alias _ = S.opBinary!"+"; }));