www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - opDispatch and operator overloads

reply "John Colvin" <john.loughran.colvin gmail.com> writes:
struct S {
	auto opDispatch(string s)(A i){}
}

struct A {}

void main() {
	S s;
	A a;
	s + a; //Error: incompatible types for ((s) + (a)): 'S' and 'A'
}

It would be really nice if opDispatch could catch missing 
operator overloads.

Also, would it be a good idea to have free functions of all the 
operators (opOpAssign etc...) for builtin types somewhere? It's 
occasionally useful in generic wrappers.
May 20 2013
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, May 20, 2013 17:15:32 John Colvin wrote:
 Also, would it be a good idea to have free functions of all the
 operators (opOpAssign etc...) for builtin types somewhere? It's
 occasionally useful in generic wrappers.

Why would this be useful? I think that it's just begging for trouble to be able to add stuff like "foo" + "bar" to the language via free functions. We don't _want_ that to be legal. That's why we have ~ in the first place. If you need to do something that you want to work with built-in types, and their operators don't do what you want, then just use a normal function rather than an operator. If you can't model your overloaded operator after what an operator does for the built-in types, it's arguably a bad choice to use an overloaded operator for that in the first place. - Jonathan M Davis
May 20 2013
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 05/20/2013 07:19 PM, Jonathan M Davis wrote:
 On Monday, May 20, 2013 17:15:32 John Colvin wrote:
 Also, would it be a good idea to have free functions of all the
 operators (opOpAssign etc...) for builtin types somewhere? It's
 occasionally useful in generic wrappers.

Why would this be useful? I think that it's just begging for trouble to be able to add stuff like "foo" + "bar" to the language via free functions. We don't _want_ that to be legal. That's why we have ~ in the first place. If you need to do something that you want to work with built-in types, and their operators don't do what you want, then just use a normal function rather than an operator. If you can't model your overloaded operator after what an operator does for the built-in types, it's arguably a bad choice to use an overloaded operator for that in the first place. - Jonathan M Davis

He just wants to be able to write: assert(2.opBinary!"+"(3) == 5); Which is entirely reasonable. This could be fixed by adding the corresponding built-in member function templates to the primitive types. (Note that this would also disallow adding eg. "foo" + "bar" even when the UFCS/op-overload incompatibility is fixed.)
May 20 2013
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 05/20/2013 05:15 PM, John Colvin wrote:
 struct S {
      auto opDispatch(string s)(A i){}
 }

 struct A {}

 void main() {
      S s;
      A a;
      s + a; //Error: incompatible types for ((s) + (a)): 'S' and 'A'
 }

 It would be really nice if opDispatch could catch missing operator
 overloads.

Agreed. DMD arbitrarily refuses a combination of language features. I think it is a compiler bug.
 Also, would it be a good idea to have free functions of all the
 operators (opOpAssign etc...) for builtin types somewhere?

I think built-in members are a better choice than free functions, as the operators are built-in. (Also, note that operator overloading is implemented in DMD in such a way that it is incompatible with UFCS, which does not match the language specification either).
 It's occasionally useful in generic wrappers.

Yes. Maybe you could file a bug report against the opDispatch part and an enhancement request for the built-in members part. http://d.puremagic.com/issues/
May 20 2013
prev sibling next sibling parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Monday, 20 May 2013 at 15:15:33 UTC, John Colvin wrote:
 struct S {
 	auto opDispatch(string s)(A i){}
 }

 struct A {}

 void main() {
 	S s;
 	A a;
 	s + a; //Error: incompatible types for ((s) + (a)): 'S' and 'A'
 }

 It would be really nice if opDispatch could catch missing 
 operator overloads.

This would also leads to bugs when invalid code is silently accepted in each user-defined type where opDispatch is defined.
 Also, would it be a good idea to have free functions of all the 
 operators (opOpAssign etc...) for builtin types somewhere? It's 
 occasionally useful in generic wrappers.

And this is pushing UFCS beyond its purpose for the sake of temporal convenience at the expense of language.
May 20 2013
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/20/2013 09:26 PM, Maxim Fomin wrote:
 On Monday, 20 May 2013 at 15:15:33 UTC, John Colvin wrote:
 struct S {
     auto opDispatch(string s)(A i){}
 }

 struct A {}

 void main() {
     S s;
     A a;
     s + a; //Error: incompatible types for ((s) + (a)): 'S' and 'A'
 }

 It would be really nice if opDispatch could catch missing operator
 overloads.

This would also leads to bugs when invalid code is silently accepted in each user-defined type where opDispatch is defined.

This statement is wrong. As a counterexample, consider the following code: struct S{ void opDispatch(string s)()if(s=="foo"){ } } Also, I'd claim that there is no way of constructing an example where the change leads to actual problems. (It is always possible to manually remove the syntax sugar to get an equivalent program without operator overloading.) The current situation where there exist types for which a.opBinary!"+"(b) is valid but not a+b is confusing at best.
 Also, would it be a good idea to have free functions of all the
 operators (opOpAssign etc...) for builtin types somewhere? It's
 occasionally useful in generic wrappers.

And this is pushing UFCS beyond its purpose for the sake of temporal convenience at the expense of language.

He was assuming, based on the language spec I suppose, that UFCS is compatible with operator overloading. His use case is orthogonal to that issue.
May 20 2013
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/20/2013 10:08 PM, Maxim Fomin wrote:
 On Monday, 20 May 2013 at 19:48:48 UTC, Timon Gehr wrote:
 On 05/20/2013 09:26 PM, Maxim Fomin wrote:
 This would also leads to bugs when invalid code is silently accepted in
 each user-defined type where opDispatch is defined.

This statement is wrong. As a counterexample, consider the following code: struct S{ void opDispatch(string s)()if(s=="foo"){ } } Also, I'd claim that there is no way of constructing an example where the change leads to actual problems. (It is always possible to manually remove the syntax sugar to get an equivalent program without operator overloading.) The current situation where there exist types for which a.opBinary!"+"(b) is valid but not a+b is confusing at best.

I refer to more complex cases where fear introduction of subtle bugs caused by combination of buggy implementation and unintended consequences which would be revealed as unexpected behavior change of existing features.

Are you saying that the compiler is not worth fixing?
 In some notorious cases such artificiality created
 problems (for local convenience at the expense of language consistency)

I don't see how this is relevant. The compiler behaviour is at the expense of language consistency, as I have argued above.
 are not easy to fix (ref), or are unfixable by design ( disable), or are
 broken and dumped without touch for ages (delegates).

I disagree with those assertions. What is the conceived issue with delegates?
 Clearly, bugs should not stop from implementing a good feature, but here
 I see low benefits and some problems.
...

This is not about implementing a new feature. This is about fixing an implementation bug. Otherwise the compiler behaviour would need to be carried over to the spec. Doing nothing about it is not valid.
May 20 2013
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/21/2013 10:40 AM, Maxim Fomin wrote:
 On Monday, 20 May 2013 at 23:10:48 UTC, Timon Gehr wrote:
 ...
 In some notorious cases such artificiality created
 problems (for local convenience at the expense of language consistency)

I don't see how this is relevant. The compiler behaviour is at the expense of language consistency, as I have argued above.
 are not easy to fix (ref), or are unfixable by design ( disable), or are
 broken and dumped without touch for ages (delegates).

I disagree with those assertions. What is the conceived issue with delegates?

Conceived issue is that they are holes in type system (unlike casts and unions they are not supposed to be), let alone there numerous bugs related to their implementation. See Bugzilla for particular examples.

I see. I reported a few of those recently. Some of them got fixed.
 Clearly, bugs should not stop from implementing a good feature, but here
 I see low benefits and some problems.
 ...

This is not about implementing a new feature. This is about fixing an implementation bug. Otherwise the compiler behaviour would need to be carried over to the spec. Doing nothing about it is not valid.

You *think* that it is a bug and this cliam is not necessaruly true.

A compiler bug is a discrepancy between spec and compiler. It is a bug given the current spec.
 According to spec you are promised to have a.foo(b) from
 foo(a,b) but you are not promissed to have anything beyond that

The fundamental problem is obviously that the "spec" is not fully unambiguous, but consider: https://en.wikipedia.org/wiki/Abstract_rewriting_system You are free to disagree by using different than usual fundamental notions, but if those are to be used by the language, they need to be defined by the spec. E.g. we'd need to introduce hidden state into the AST: Rewritten code would not be D code anymore, which removes most of the benefits of using rewrite rules for specification in the first place. (but given that the rewriting system given by the various rules from the spec does not appear to be confluent, this is a moot point anyway.)
 (including several stages of rewrite for operator overloading).

Given the assumption that the spec implies a limit on the number of such "stages", why would the limit be 1 and not 2 or 3? Why not even other numbers?
 Please stop presenting a POV as absolute truth.

Mathematical truth. To disagree with it you'd either: [1] Question the axioms or definitions. => In this case, my argument is valid, but this is not relevant for => you. I guess most of the arguing about language features goes on in => this section. To settle disputes here, concrete examples showing => the merits of your own viewpoint are usually useful. [2] Show a flaw in reasoning. [3] Be wrong.
May 21 2013
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 05/21/2013 08:24 PM, Maxim Fomin wrote:
 ...

 According to the spec (overload page and TDPL) operators are rewritten
 specifically named *member* functions and calling *member* function is
 not the same thing as syntax to call free functions like member
 functions due to:
 - non-member (UFCS) functions cannot be virtualized and overridden
 - they do not appear in get members traits
 - they cannot access all fields in general.

This is not too relevant for the current discussion since we are discussing operator overloading using opDispatch, which can be a member function.
 Using your rewriting notation

Not really.
 operator overloading is defined as (a -
 expression, b - some member function other than opDispatch, c -
 opDispatch): if (a && b) then a->b else error. Opdispatch is defined as:
 if(!b && c) then b->c else error.

I guess I understand what you mean, but you are missing the specification of Eg. the opSlice rewrite which would render the discussion meaningful. Also, the language spec does not suggest a differentiation as is present in those rules between a and b.
 So, compiler works according to the spec.

 By the way, your logic for a->b->c leads to following flaw:

 class A
 {
     void opSlice(){}
     void opDispatch(){}
 }
 ..
 a[] // unconditionally calls opDispatch since '[]'->'opSlice'->'opDispatch'

Using your notation, if you assume there is both a rewrite '[]'->'opSlice' and 'opSlice'->'opDispatch' then yes. The assumption of existence of a rewrite 'opSlice'->'opDispatch' is what introduces the flaw. The spec correctly states that there is no 'opSlice'->'opDispatch' rewrite in this case, as it can be resolved otherwise.
May 21 2013
prev sibling next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Monday, 20 May 2013 at 19:48:48 UTC, Timon Gehr wrote:
 On 05/20/2013 09:26 PM, Maxim Fomin wrote:
 This would also leads to bugs when invalid code is silently 
 accepted in
 each user-defined type where opDispatch is defined.

This statement is wrong. As a counterexample, consider the following code: struct S{ void opDispatch(string s)()if(s=="foo"){ } } Also, I'd claim that there is no way of constructing an example where the change leads to actual problems. (It is always possible to manually remove the syntax sugar to get an equivalent program without operator overloading.) The current situation where there exist types for which a.opBinary!"+"(b) is valid but not a+b is confusing at best.

I refer to more complex cases where fear introduction of subtle bugs caused by combination of buggy implementation and unintended consequences which would be revealed as unexpected behavior change of existing features. In some notorious cases such artificiality created problems (for local convenience at the expense of language consistency) are not easy to fix (ref), or are unfixable by design ( disable), or are broken and dumped without touch for ages (delegates). Clearly, bugs should not stop from implementing a good feature, but here I see low benefits and some problems.
 Also, would it be a good idea to have free functions of all 
 the
 operators (opOpAssign etc...) for builtin types somewhere? 
 It's
 occasionally useful in generic wrappers.

And this is pushing UFCS beyond its purpose for the sake of temporal convenience at the expense of language.

He was assuming, based on the language spec I suppose, that UFCS is compatible with operator overloading. His use case is orthogonal to that issue.

Yes, it is separate story.
May 20 2013
prev sibling next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Monday, 20 May 2013 at 23:10:48 UTC, Timon Gehr wrote:
 On 05/20/2013 10:08 PM, Maxim Fomin wrote:
 I refer to more complex cases where fear introduction of 
 subtle bugs
 caused by combination of buggy implementation and unintended
 consequences which would be revealed as unexpected behavior 
 change of
 existing features.

Are you saying that the compiler is not worth fixing?

In the bottom of the comment I answered to this.
 In some notorious cases such artificiality created
 problems (for local convenience at the expense of language 
 consistency)

I don't see how this is relevant. The compiler behaviour is at the expense of language consistency, as I have argued above.
 are not easy to fix (ref), or are unfixable by design 
 ( disable), or are
 broken and dumped without touch for ages (delegates).

I disagree with those assertions. What is the conceived issue with delegates?

Conceived issue is that they are holes in type system (unlike casts and unions they are not supposed to be), let alone there numerous bugs related to their implementation. See Bugzilla for particular examples.
 Clearly, bugs should not stop from implementing a good 
 feature, but here
 I see low benefits and some problems.
...

This is not about implementing a new feature. This is about fixing an implementation bug. Otherwise the compiler behaviour would need to be carried over to the spec. Doing nothing about it is not valid.

You *think* that it is a bug and this cliam is not necessaruly true. According to spec you are promised to have a.foo(b) from foo(a,b) but you are not promissed to have anything beyond that (including several stages of rewrite for operator overloading). Please stop presenting a POV as absolute truth.
May 21 2013
prev sibling next sibling parent "John Colvin" <john.loughran.colvin gmail.com> writes:
On Monday, 20 May 2013 at 19:26:28 UTC, Maxim Fomin wrote:
 On Monday, 20 May 2013 at 15:15:33 UTC, John Colvin wrote:
 struct S {
 	auto opDispatch(string s)(A i){}
 }

 struct A {}

 void main() {
 	S s;
 	A a;
 	s + a; //Error: incompatible types for ((s) + (a)): 'S' and 
 'A'
 }

 It would be really nice if opDispatch could catch missing 
 operator overloads.

This would also leads to bugs when invalid code is silently accepted in each user-defined type where opDispatch is defined.

Could you please expand on why / give an example? Seeing as all operators (only on user defined types, sadly) can be rewritten as op***** member functions, I don't see why having opDispatch catch missing operators is any different from it catching missing member function calls, as it currently does.
May 21 2013
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 20 May 2013 11:15:32 -0400, John Colvin  
<john.loughran.colvin gmail.com> wrote:

 struct S {
 	auto opDispatch(string s)(A i){}
 }

 struct A {}

 void main() {
 	S s;
 	A a;
 	s + a; //Error: incompatible types for ((s) + (a)): 'S' and 'A'
 }

 It would be really nice if opDispatch could catch missing operator  
 overloads.

Not sure this can work. opDispatch takes a string identifying the function name, but opBinary is a template that ALSO needs a string. I suppose the string parameter to opDispatch could be the explicit instantiation: s.opDispatch!"opBinary!\"+\""(a) but that would seem extremely difficult to handle, you'd kind of need a parser to deal with it. what is the problem with just defining opBinary to catch missing operator overloads?
 Also, would it be a good idea to have free functions of all the  
 operators (opOpAssign etc...) for builtin types somewhere? It's  
 occasionally useful in generic wrappers.

The name of operator overloads are the actual operators. The opOpAssign, etc, are hooks. Use the actual operators in generic code. A use case to discuss may be more helpful. -Steve
May 21 2013
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/21/2013 05:31 PM, Steven Schveighoffer wrote:
 On Mon, 20 May 2013 11:15:32 -0400, John Colvin
 <john.loughran.colvin gmail.com> wrote:

 struct S {
     auto opDispatch(string s)(A i){}
 }

 struct A {}

 void main() {
     S s;
     A a;
     s + a; //Error: incompatible types for ((s) + (a)): 'S' and 'A'
 }

 It would be really nice if opDispatch could catch missing operator
 overloads.

Not sure this can work. opDispatch takes a string identifying the function name, but opBinary is a template that ALSO needs a string. I suppose the string parameter to opDispatch could be the explicit instantiation: s.opDispatch!"opBinary!\"+\""(a)

It is parsed in a different way as is assumed here, the AST after opDispatch rewrite looks like this: (s.opDispatch!"opBinary")!"+"(a); (which is a parse error with DMD due to a parser bug. It assumes this is a C-style cast and bails out. The internal AST representation allows this just fine though.)
 but that would seem extremely difficult to handle, you'd kind of need a
 parser to deal with it.

This handles your case using opDispatch: import std.stdio,std.conv,std.algorithm,std.array; string commaSep(T...)(T args){ string r=""; foreach(a;args) r~=a.to!string~","; return r[0..$-!!$]; } struct S{ template opDispatch(string name){ template opDispatch(T...){ auto opDispatch(S...)(S args){ writeln(this,".",name,"!"~T.stringof~"(",commaSep(args),")"); } } } } void main(){ S s; s.opBinary!"+"(2); // ok // s.foo(); // error. should IMO be fixed. s.foo!()(); // ok s.bar!([1,2,3],int)("123"); // ok }
 what is the problem with just defining opBinary to catch missing
 operator overloads?

It is more boilerplate. https://github.com/D-Programming-Language/phobos/blob/bcf7dd9bd268956754bf1a034728bef29619e858/std/typecons.d#L2654
 Also, would it be a good idea to have free functions of all the
 operators (opOpAssign etc...) for builtin types somewhere? It's
 occasionally useful in generic wrappers.

The name of operator overloads are the actual operators. The opOpAssign, etc, are hooks. Use the actual operators in generic code. A use case to discuss may be more helpful.

Eg. use a single opDispatch to forward missing operations to a built-in member. The opDispatch/operator overloading limitation is not the only thing that currently prevents this from working nicely though. (eg. opDispatch does not naturally support variable-length instantiation chains.)
May 21 2013
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 05/21/2013 09:53 PM, Steven Schveighoffer wrote:
 On Tue, 21 May 2013 15:36:31 -0400, Timon Gehr <timon.gehr gmx.ch> wrote:

 On 05/21/2013 05:31 PM, Steven Schveighoffer wrote:
 ...

This handles your case using opDispatch: import std.stdio,std.conv,std.algorithm,std.array; string commaSep(T...)(T args){ string r=""; foreach(a;args) r~=a.to!string~","; return r[0..$-!!$]; } struct S{ template opDispatch(string name){ template opDispatch(T...){ auto opDispatch(S...)(S args){ writeln(this,".",name,"!"~T.stringof~"(",commaSep(args),")"); } } } } void main(){ S s; s.opBinary!"+"(2); // ok // s.foo(); // error. should IMO be fixed. s.foo!()(); // ok s.bar!([1,2,3],int)("123"); // ok }
 what is the problem with just defining opBinary to catch missing
 operator overloads?

It is more boilerplate. https://github.com/D-Programming-Language/phobos/blob/bcf7dd9bd268956754bf1a034728bef29619e858/std/typecons.d#L2654

This pretty much makes my point. I have no idea what your 3-level opDispatch does.

1st level: member name 2nd level: explicit template arguments 3rd level: ifti-deduced arguments.
 The proxy type, while verbose, I can understand.

Fair point.
 Yes, lots of boilerplate.  If anything, though, that's an argument to
 fix how operator overloading works.

 At the end of the day, it's going to be less boilerplate for the
 std.typecons.Proxy that one has to mixin.  User code will look the same.
...

For user code there is a difference eg. in how inflated the __traits(allMembers) result will be.
May 21 2013
prev sibling next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Tuesday, 21 May 2013 at 10:53:04 UTC, Timon Gehr wrote:
 On 05/21/2013 10:40 AM, Maxim Fomin wrote:
 On Monday, 20 May 2013 at 23:10:48 UTC, Timon Gehr wrote:
 ...
 In some notorious cases such artificiality created
 problems (for local convenience at the expense of language 
 consistency)

I don't see how this is relevant. The compiler behaviour is at the expense of language consistency, as I have argued above.
 are not easy to fix (ref), or are unfixable by design 
 ( disable), or are
 broken and dumped without touch for ages (delegates).

I disagree with those assertions. What is the conceived issue with delegates?

Conceived issue is that they are holes in type system (unlike casts and unions they are not supposed to be), let alone there numerous bugs related to their implementation. See Bugzilla for particular examples.

I see. I reported a few of those recently. Some of them got fixed.
 Clearly, bugs should not stop from implementing a good 
 feature, but here
 I see low benefits and some problems.
 ...

This is not about implementing a new feature. This is about fixing an implementation bug. Otherwise the compiler behaviour would need to be carried over to the spec. Doing nothing about it is not valid.

You *think* that it is a bug and this cliam is not necessaruly true.

A compiler bug is a discrepancy between spec and compiler. It is a bug given the current spec.
 According to spec you are promised to have a.foo(b) from
 foo(a,b) but you are not promissed to have anything beyond that

The fundamental problem is obviously that the "spec" is not fully unambiguous, but consider: https://en.wikipedia.org/wiki/Abstract_rewriting_system You are free to disagree by using different than usual fundamental notions, but if those are to be used by the language, they need to be defined by the spec. E.g. we'd need to introduce hidden state into the AST: Rewritten code would not be D code anymore, which removes most of the benefits of using rewrite rules for specification in the first place. (but given that the rewriting system given by the various rules from the spec does not appear to be confluent, this is a moot point anyway.)
 (including several stages of rewrite for operator overloading).

Given the assumption that the spec implies a limit on the number of such "stages", why would the limit be 1 and not 2 or 3? Why not even other numbers?
 Please stop presenting a POV as absolute truth.

Mathematical truth. To disagree with it you'd either: [1] Question the axioms or definitions. => In this case, my argument is valid, but this is not relevant for => you. I guess most of the arguing about language features goes on in => this section. To settle disputes here, concrete examples showing => the merits of your own viewpoint are usually useful. [2] Show a flaw in reasoning. [3] Be wrong.

According to the spec (overload page and TDPL) operators are rewritten specifically named *member* functions and calling *member* function is not the same thing as syntax to call free functions like member functions due to: - non-member (UFCS) functions cannot be virtualized and overridden - they do not appear in get members traits - they cannot access all fields in general. Using your rewriting notation operator overloading is defined as (a - expression, b - some member function other than opDispatch, c - opDispatch): if (a && b) then a->b else error. Opdispatch is defined as: if(!b && c) then b->c else error. So, compiler works according to the spec. By the way, your logic for a->b->c leads to following flaw: class A { void opSlice(){} void opDispatch(){} } .. a[] // unconditionally calls opDispatch since '[]'->'opSlice'->'opDispatch'
May 21 2013
prev sibling next sibling parent "John Colvin" <john.loughran.colvin gmail.com> writes:
On Monday, 20 May 2013 at 19:26:28 UTC, Maxim Fomin wrote:
 And this is pushing UFCS beyond its purpose for the sake of 
 temporal convenience at the expense of language.

I suspect you're correct on this. The only reason I suggested using UFCS was that it could be a library solution as opposed to a language/compiler modification.
May 21 2013
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 21 May 2013 15:36:31 -0400, Timon Gehr <timon.gehr gmx.ch> wrote:

 On 05/21/2013 05:31 PM, Steven Schveighoffer wrote:
 On Mon, 20 May 2013 11:15:32 -0400, John Colvin
 <john.loughran.colvin gmail.com> wrote:

 struct S {
     auto opDispatch(string s)(A i){}
 }

 struct A {}

 void main() {
     S s;
     A a;
     s + a; //Error: incompatible types for ((s) + (a)): 'S' and 'A'
 }

 It would be really nice if opDispatch could catch missing operator
 overloads.

Not sure this can work. opDispatch takes a string identifying the function name, but opBinary is a template that ALSO needs a string. I suppose the string parameter to opDispatch could be the explicit instantiation: s.opDispatch!"opBinary!\"+\""(a)

It is parsed in a different way as is assumed here, the AST after opDispatch rewrite looks like this: (s.opDispatch!"opBinary")!"+"(a);

OK, so the result of opDispatch needs to be a template, not a function. OK, I can follow that
 but that would seem extremely difficult to handle, you'd kind of need a
 parser to deal with it.

This handles your case using opDispatch: import std.stdio,std.conv,std.algorithm,std.array; string commaSep(T...)(T args){ string r=""; foreach(a;args) r~=a.to!string~","; return r[0..$-!!$]; } struct S{ template opDispatch(string name){ template opDispatch(T...){ auto opDispatch(S...)(S args){ writeln(this,".",name,"!"~T.stringof~"(",commaSep(args),")"); } } } } void main(){ S s; s.opBinary!"+"(2); // ok // s.foo(); // error. should IMO be fixed. s.foo!()(); // ok s.bar!([1,2,3],int)("123"); // ok }
 what is the problem with just defining opBinary to catch missing
 operator overloads?

It is more boilerplate. https://github.com/D-Programming-Language/phobos/blob/bcf7dd9bd268956754bf1a034728bef29619e858/std/typecons.d#L2654

This pretty much makes my point. I have no idea what your 3-level opDispatch does. The proxy type, while verbose, I can understand. Yes, lots of boilerplate. If anything, though, that's an argument to fix how operator overloading works. At the end of the day, it's going to be less boilerplate for the std.typecons.Proxy that one has to mixin. User code will look the same. -Steve
May 21 2013
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 21 May 2013 16:16:08 -0400, Timon Gehr <timon.gehr gmx.ch> wrote:

 On 05/21/2013 09:53 PM, Steven Schveighoffer wrote:

 At the end of the day, it's going to be less boilerplate for the
 std.typecons.Proxy that one has to mixin.  User code will look the same.
 ...

For user code there is a difference eg. in how inflated the __traits(allMembers) result will be.

I consider it a weak argument. I wouldn't expect the __traits(allMembers) to be meaningful for a proxy type. As you want it, it would have two members, opDispatch, and the wrapped member. I don't know how useful that would be. Admittedly, I don't regularly use the allMembers trait. -Steve
May 21 2013