www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Best way of checking for a templated function instantiation

reply Arafel <er.krali gmail.com> writes:
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!"-"));
}
---
Aug 10 2016
next sibling parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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)));
Aug 10 2016
prev sibling next sibling parent reply Meta <jared771 gmail.com> writes:
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
Aug 10 2016
parent reply Meta <jared771 gmail.com> writes:
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); }));
Aug 10 2016
parent reply Arafel <er.krali gmail.com> writes:
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!\"+\"")
Aug 10 2016
next sibling parent Jonathan M Davis via Digitalmars-d-learn writes:
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
Aug 10 2016
prev sibling parent Meta <jared771 gmail.com> writes:
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!"+"; }));
Aug 10 2016
prev sibling parent Jonathan M Davis via Digitalmars-d-learn writes:
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
Aug 10 2016