www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Getting the overload set of a template

reply Arafel <er.krali gmail.com> writes:
Hi!

Is there any way to get the full set of templates that are "overloaded" 
(in my case, based on constraints)?

Basically, I'd like to make something like 
https://run.dlang.io/is/z2LeAj return both versions of the template (and 
then retrieve their UDAs)...

If it's not possible, I can still work around it, but I find it a bit 
frustrating that you can only access the first version of a template.

Thanks!

Arafel
Apr 17 2018
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 17 April 2018 at 14:22:27 UTC, Arafel wrote:
 Hi!

 Is there any way to get the full set of templates that are 
 "overloaded" (in my case, based on constraints)?
Currently, there is no way (that I've found, at least) to do this. If you have a workaround, that's great, but there really should be a way - probably __traits(getOverloads). Having __traits(getOverloads) return templates as well should fix some of the issues __traits(getOverloads) has, as a bonus. -- Simen
Apr 19 2018
next sibling parent reply Alex <sascha.orlov gmail.com> writes:
On Thursday, 19 April 2018 at 13:57:04 UTC, Simen Kjærås wrote:
 Currently, there is no way (that I've found, at least) to do 
 this. If you have a workaround, that's great, but there really 
 should be a way - probably __traits(getOverloads). Having 
 __traits(getOverloads) return templates as well should fix some 
 of the issues __traits(getOverloads) has, as a bonus.
Would it be possible at all? I mean, if the two following codes are equivalent ´´´ S("Has foo_A") template foo(string s) if (s == "a") { enum foo = "foo_A"; } S("Has foo_B") template foo(string s) if (s == "b") { enum foo = "foo_B"; } ´´´ ´´´ template foo(string s) { static if (s == "a") { S("Has foo_A") enum foo = "foo_A"; } else static if (s == "b") { S("Has foo_B") enum foo = "foo_B"; } } ´´´ How would you define a "template overload"? And which "overloads" would you like to get if constraints are more general? And last but not least, the getOverloads is defined on functions, which are callable, whereas templates are not, in general...
Apr 19 2018
next sibling parent Arafel <er.krali gmail.com> writes:
Well, if that's the lowering, then it's indeed hard. That doesn't mean 
it shouldn't happen, though... perhaps changing the lowering? I'm no 
compiles expert, so no idea how).

What I'd like to get is the same that I get using 
__traits(getMember,...), but repeated n times (AliasSeq perhaps?), like 
with regular overloads.

Then, whatever I can do with the first entry (the only one I can get 
currently) should also be possible with the rest. In my case, I'd like 
to access the UDAs, but I can imagine that the use case that allows us 
to get a template keeps being valid for all the "hidden" alternatives.

Also, I think that whether to use "getOverloads" or to add a new trait 
is rather an implementation detail

It's a bit frustrating being able to access only the first of a set...

A.

 Would it be possible at all? I mean, if the two following codes are 
equivalent
 ´´´
       S("Has foo_A") template foo(string s) if (s == "a") {
          enum foo = "foo_A";
      }
       S("Has foo_B") template foo(string s) if (s == "b") {
          enum foo = "foo_B";
      }
 ´´´


 ´´´
      template foo(string s)
      {
          static if (s == "a")
          {
                  S("Has foo_A") enum foo = "foo_A";
          }
          else static if (s == "b")
          {
                 S("Has foo_B") enum foo = "foo_B";
          }
      }
 ´´´

 How would you define a "template overload"?
 And which "overloads" would you like to get if constraints are more 
general?
 And last but not least, the getOverloads is defined on functions, 
which are callable, whereas templates are not, in general...
Apr 19 2018
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Thursday, 19 April 2018 at 14:16:21 UTC, Alex wrote:
 On Thursday, 19 April 2018 at 13:57:04 UTC, Simen Kjærås wrote:
 Currently, there is no way (that I've found, at least) to do 
 this. If you have a workaround, that's great, but there really 
 should be a way - probably __traits(getOverloads). Having 
 __traits(getOverloads) return templates as well should fix 
 some of the issues __traits(getOverloads) has, as a bonus.
Would it be possible at all? I mean, if the two following codes are equivalent ´´´ S("Has foo_A") template foo(string s) if (s == "a") { enum foo = "foo_A"; } S("Has foo_B") template foo(string s) if (s == "b") { enum foo = "foo_B"; } ´´´ ´´´ template foo(string s) { static if (s == "a") { S("Has foo_A") enum foo = "foo_A"; } else static if (s == "b") { S("Has foo_B") enum foo = "foo_B"; } } ´´´ How would you define a "template overload"? And which "overloads" would you like to get if constraints are more general? And last but not least, the getOverloads is defined on functions, which are callable, whereas templates are not, in general...
Your first example defines two templates (which are overloads of the same name), the second only one. There's no ambiguity there. Since template instantiation is analogous to function calling (though one is a compile-time action, the other a run-time one), talking about template overloads in the same way as function overloads makes perfect sense. Whether the result of instantiating the template is a function, a value, or a type, it's an overload of the template. -- Simen
Apr 19 2018
parent reply Alex <sascha.orlov gmail.com> writes:
On Thursday, 19 April 2018 at 17:55:47 UTC, Simen Kjærås wrote:
 Your first example defines two templates (which are overloads 
 of the same name), the second only one. There's no ambiguity 
 there.
So, do you mean, that the constraint belongs to the interface of a template?
Apr 21 2018
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Saturday, 21 April 2018 at 11:23:33 UTC, Alex wrote:
 On Thursday, 19 April 2018 at 17:55:47 UTC, Simen Kjærås wrote:
 Your first example defines two templates (which are overloads 
 of the same name), the second only one. There's no ambiguity 
 there.
So, do you mean, that the constraint belongs to the interface of a template?
Not necessarily - it depends on what you want to achieve. The only thing I mean is that the code clearly defines two templates in one case (and chooses which template to instantiate based on the arguments), and one template in the other (and chooses the contents of that template based on the arguments). Sure, the end result may be similar*, but how it works in the language is clearly defined, and different between the two cases. *In this case, there are important differences - in the first case the template itself is marked with a UDA, in the second the enum member is. In the first case foo!"c" will fail to instantiate, in the second it won't. -- Simen
Apr 21 2018
parent reply Alex <sascha.orlov gmail.com> writes:
On Saturday, 21 April 2018 at 19:51:05 UTC, Simen Kjærås wrote:
 On Saturday, 21 April 2018 at 11:23:33 UTC, Alex wrote:
 So, do you mean, that the constraint belongs to the interface 
 of a template?
Not necessarily - it depends on what you want to achieve. The only thing I mean is that the code clearly defines two templates in one case (and chooses which template to instantiate based on the arguments), and one template in the other (and chooses the contents of that template based on the arguments).
This is wrong, at least as I understand this: Lowering rules apply. These rules define a set of semantic equivalent constructs. I expect all semantic equivalent constructs to be handled equivalent.**
 Sure, the end result may be similar*, but how it works in the 
 language is clearly defined, and different between the two 
 cases.

 *In this case, there are important differences - in the first 
 case the template itself is marked with a UDA, in the second 
 the enum member is.
This is a good point. But you won't be able to get the UDA from an uninstantiated template will you? If you will, then, I'm wrong, I think... ** So my point is: If you can transform some construct into another one by lowering rules, than - either: you can't define an overload - or: you can define same overloads on both forms. [1]
 In the first case foo!"c" will fail to instantiate, in the 
 second it won't.
This is due, I didn't add ´´´ else { assert(0); } ´´´ sorry for this. [1] http://www.drdobbs.com/architecture-and-design/so-you-want-to-write-your-own-language/240165488?pgno=2
Apr 21 2018
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Saturday, 21 April 2018 at 21:10:29 UTC, Alex wrote:
 On Saturday, 21 April 2018 at 19:51:05 UTC, Simen Kjærås wrote:
 On Saturday, 21 April 2018 at 11:23:33 UTC, Alex wrote:
 So, do you mean, that the constraint belongs to the interface 
 of a template?
Not necessarily - it depends on what you want to achieve. The only thing I mean is that the code clearly defines two templates in one case (and chooses which template to instantiate based on the arguments), and one template in the other (and chooses the contents of that template based on the arguments).
This is wrong, at least as I understand this: Lowering rules apply. These rules define a set of semantic equivalent constructs. I expect all semantic equivalent constructs to be handled equivalent.**
No lowering occurs here. A lowering is when the compiler takes one piece of syntax and replaces it with a different one, usually one that's more verbose. In a way, it's kind of like a template being instantiated, in that you write some code and it's being replaced by something else. It's not, however, the same thing. Templates are sort of like compile-time functions - overloads are chosen in a similar way way to how function overloads are chosen, and the result may depend on the parameters given. Two templates, just like two functions, can give the same result without the templates themselves being the same.
 *In this case, there are important differences - in the first 
 case the template itself is marked with a UDA, in the second 
 the enum member is.
This is a good point. But you won't be able to get the UDA from an uninstantiated template will you? If you will, then, I'm wrong, I think...
Why not try it? ("Has foo1_A") template foo1(string s) if (s == "a") { enum foo1 = "foo1_A"; } ("Has foo1_B") template foo1(string s) if (s == "b") { enum foo1 = "foo1_B"; } template foo2(string s) { static if (s == "a") { ("Has foo2_A") enum foo2 = "foo2_A"; } else static if (s == "b") { ("Has foo2_B") enum foo2 = "foo2_B"; } } // tuple("Has foo1_A") pragma(msg, __traits(getAttributes, foo1)); // tuple() pragma(msg, __traits(getAttributes, foo2)); So yes, we can. Templates are real things and can be manipulated, passed to other templates, etc. -- Simen
Apr 22 2018
parent reply Alex <sascha.orlov gmail.com> writes:
On Sunday, 22 April 2018 at 18:25:29 UTC, Simen Kjærås wrote:
 No lowering occurs here. A lowering is when the compiler takes 
 one piece of syntax and replaces it with a different one, 
 usually one that's more verbose.

 In a way, it's kind of like a template being instantiated, in 
 that you write some code and it's being replaced by something 
 else. It's not, however, the same thing.
So, how to distinguish it?
 Templates are sort of like compile-time functions - overloads 
 are chosen in a similar way way to how function overloads are 
 chosen, and the result may depend on the parameters given. Two 
 templates, just like two functions, can give the same result 
 without the templates themselves being the same.


 *In this case, there are important differences - in the first 
 case the template itself is marked with a UDA, in the second 
 the enum member is.
This is a good point. But you won't be able to get the UDA from an uninstantiated template will you? If you will, then, I'm wrong, I think...
Why not try it? ("Has foo1_A") template foo1(string s) if (s == "a") { enum foo1 = "foo1_A"; } ("Has foo1_B") template foo1(string s) if (s == "b") { enum foo1 = "foo1_B"; } template foo2(string s) { static if (s == "a") { ("Has foo2_A") enum foo2 = "foo2_A"; } else static if (s == "b") { ("Has foo2_B") enum foo2 = "foo2_B"; } } // tuple("Has foo1_A") pragma(msg, __traits(getAttributes, foo1)); // tuple() pragma(msg, __traits(getAttributes, foo2)); So yes, we can. Templates are real things and can be manipulated, passed to other templates, etc.
The example you give, is not what I mean. Compare it with this: ("Has foo1_A") template foo1(string s) if (s == "a") { enum foo1 = "foo1_A"; } ("Has foo1_B") template foo1(string s) if (s == "b") { enum foo1 = "foo1_B"; } template foo2(string s) { static if (s == "a") { ("Has foo2_A") enum foo2 = "foo2_A"; } else static if (s == "b") { ("Has foo2_B") enum foo2 = "foo2_B"; } } // tuple("Has foo1_A") pragma(msg, __traits(getAttributes, foo1!"a")); // tuple("Has foo1_A") pragma(msg, __traits(getAttributes, foo2!"a")); // tuple("Has foo1_B") pragma(msg, __traits(getAttributes, foo1!"b")); // tuple("Has foo1_B") pragma(msg, __traits(getAttributes, foo2!"b"));
Apr 22 2018
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Sunday, 22 April 2018 at 19:27:24 UTC, Alex wrote:
 On Sunday, 22 April 2018 at 18:25:29 UTC, Simen Kjærås wrote:
 No lowering occurs here. A lowering is when the compiler takes 
 one piece of syntax and replaces it with a different one, 
 usually one that's more verbose.

 In a way, it's kind of like a template being instantiated, in 
 that you write some code and it's being replaced by something 
 else. It's not, however, the same thing.
So, how to distinguish it?
There is a limited set of lowerings, and they are defined in the language, not in user code. They include operator overloading (where `a op b` is translated to `a.opBinary!op(b)`), foreach over ranges (where `foreach(e; range) { }` becomes `for (auto e = range.front; !range.empty(); range.popFront()) { }`), string switching (which is forwarded to a template in druntime), and more that I can't recall right now. This is compiler magic meant to make implementating new features and reasoning about existing features, easier. They are briefly described in the article you linked, but I agree it offers limited insight. I've not found any other great sources of information about it, sadly.
 // tuple("Has foo1_A")
 pragma(msg, __traits(getAttributes, foo1!"a"));
 // tuple("Has foo1_A")
 pragma(msg, __traits(getAttributes, foo2!"a"));
 // tuple("Has foo1_B")
 pragma(msg, __traits(getAttributes, foo1!"b"));
 // tuple("Has foo1_B")
 pragma(msg, __traits(getAttributes, foo2!"b"));
You explicitly stated 'uninstantiated template'. These are instantiated templates. This seems to indicate that we're talking past each other. I may have misunderstood what you meant when you wrote "But you won't be able to get the UDA from an uninstantiated template will you?", or one of us has misunderstood some other part of the discussion. -- Simen
Apr 22 2018
parent reply Alex <sascha.orlov gmail.com> writes:
On Monday, 23 April 2018 at 00:26:23 UTC, Simen Kjærås wrote:
 // tuple("Has foo1_A")
 pragma(msg, __traits(getAttributes, foo1!"a"));
 // tuple("Has foo1_A")
 pragma(msg, __traits(getAttributes, foo2!"a"));
 // tuple("Has foo1_B")
 pragma(msg, __traits(getAttributes, foo1!"b"));
 // tuple("Has foo1_B")
 pragma(msg, __traits(getAttributes, foo2!"b"));
You explicitly stated 'uninstantiated template'. These are instantiated templates. This seems to indicate that we're talking past each other. I may have misunderstood what you meant when you wrote "But you won't be able to get the UDA from an uninstantiated template will you?", or one of us has misunderstood some other part of the discussion.
That could be, but we are getting closer :) So, from the output, we can see, that the both implementation cannot be distinguished (at least by the given pragmas) However, they would be after the new getOverloads version. And I'm wondering all the time, if this is wanted, as the new getOverloads would imply, that I cannot choose between both versions freely.
 There is a limited set of lowerings, and they are defined in 
 the language, not in user code. They include operator 
 overloading (where `a op b` is translated to 
 `a.opBinary!op(b)`), foreach over ranges (where `foreach(e; 
 range) { }` becomes `for (auto e = range.front; !range.empty(); 
 range.popFront()) { }`), string switching (which is forwarded 
 to a template in druntime), and more that I can't recall right 
 now. This is compiler magic meant to make implementating new 
 features and reasoning about existing features, easier. They 
 are briefly described in the article you linked, but I agree it 
 offers limited insight. I've not found any other great sources 
 of information about it, sadly.
This is not true, in my opinion. As an example, Walter gives the rewrite of a while-loop and a foreach-loop into a for-loop, stating that by the ability to do this, the for-loop is more basic than both of the former. So, in my mind, every action of rewriting of something into something else is "lowering". And if you can do this (of course, maintaining the full semantic equivalence), available actions have to remain the same. Said this, I'm not against the new getOverloads. On the contrary, I find the feature cool. But if it is present, it has to yield the same results for foo1 and foo2, just like the pragmas. I'm not sure about the procedure... should I post this as a question to the GitHub, so it can be discussed? pull 2351 or 8195? And yes. I'm lacking information about lowering too. But, if all rewrite processes are "lowering" an explicit definition is not needed, as "everything" is allowed. It has something natural so far... However, if the PR would be accepted in the form it is now, I would make an enhancement request on the docu.
Apr 22 2018
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 23 April 2018 at 04:58:38 UTC, Alex wrote:
 On Monday, 23 April 2018 at 00:26:23 UTC, Simen Kjærås wrote:
 There is a limited set of lowerings, and they are defined in 
 the language, not in user code. They include operator 
 overloading (where `a op b` is translated to 
 `a.opBinary!op(b)`), foreach over ranges (where `foreach(e; 
 range) { }` becomes `for (auto e = range.front; 
 !range.empty(); range.popFront()) { }`), string switching 
 (which is forwarded to a template in druntime), and more that 
 I can't recall right now. This is compiler magic meant to make 
 implementating new features and reasoning about existing 
 features, easier. They are briefly described in the article 
 you linked, but I agree it offers limited insight. I've not 
 found any other great sources of information about it, sadly.
This is not true, in my opinion. As an example, Walter gives the rewrite of a while-loop and a foreach-loop into a for-loop, stating that by the ability to do this, the for-loop is more basic than both of the former. So, in my mind, every action of rewriting of something into something else is "lowering". And if you can do this (of course, maintaining the full semantic equivalence), available actions have to remain the same.
That's not the definition of lowering used elsewhere, and so will lead to confusion and misunderstanding. I would strongly suggest you rethink your definition of lowering.
 Said this, I'm not against the new getOverloads. On the 
 contrary, I find the feature cool. But if it is present, it has 
 to yield the same results for foo1 and foo2, just like the 
 pragmas.
In the general case, this is impossible. Even just limiting ourselves to simple usages of static if it gets unwieldy. This template is from Phobos (all unnecessary code removed). It has 224 different possible combinations of features: private struct _Cache(R, bool bidir) { static if (bidir) {} else {} static if (isInfinite!R) {} else {} static if (hasLength!R) {} version(assert) {} static if (isForwardRange!R) {} static if (hasSlicing!R) { static if (hasEndSlicing) {} static if (!isInfinite!R) {} else static if (hasEndSlicing) {} } } And that's before we even instantiate any templates that this template references - any templates used inside _Cache could increase the number of combinations. But wait, there's more! How many does this have? struct Bar(string s) { mixin(s); } If that's just one overload because of the impossibility of generating the options, what if we introduce a single static if inside it? Is that still one, or is it two? Do we count the possibilities introduced by a template mixin, but not by a string mixin? What if the template mixin comes from a template argument? Sorry if I come off as very dismissive right now - I kind of like the idea, but it seems impossible in practice.
 I'm not sure about the procedure... should I post this as a 
 question to the GitHub, so it can be discussed? pull 2351 or 
 8195?
What you are suggesting would be a fundamental change in the language, and should be discussed in the digitalmars.D forum. Either PR is not the right place to discuss it. If there's any interest, you will have to write a DIP for how the change will work. -- Simen
Apr 23 2018
parent reply Alex <sascha.orlov gmail.com> writes:
On Monday, 23 April 2018 at 07:49:39 UTC, Simen Kjærås wrote:
 On Monday, 23 April 2018 at 04:58:38 UTC, Alex wrote:
 On Monday, 23 April 2018 at 00:26:23 UTC, Simen Kjærås wrote:
 There is a limited set of lowerings, and they are defined in 
 the language, not in user code. They include operator 
 overloading (where `a op b` is translated to 
 `a.opBinary!op(b)`), foreach over ranges (where `foreach(e; 
 range) { }` becomes `for (auto e = range.front; 
 !range.empty(); range.popFront()) { }`), string switching 
 (which is forwarded to a template in druntime), and more that 
 I can't recall right now. This is compiler magic meant to 
 make implementating new features and reasoning about existing 
 features, easier. They are briefly described in the article 
 you linked, but I agree it offers limited insight. I've not 
 found any other great sources of information about it, sadly.
This is not true, in my opinion. As an example, Walter gives the rewrite of a while-loop and a foreach-loop into a for-loop, stating that by the ability to do this, the for-loop is more basic than both of the former. So, in my mind, every action of rewriting of something into something else is "lowering". And if you can do this (of course, maintaining the full semantic equivalence), available actions have to remain the same.
That's not the definition of lowering used elsewhere, and so will lead to confusion and misunderstanding. I would strongly suggest you rethink your definition of lowering.
There is no official definition. That's because some natural rewrite rules are implied, which are very general, I assume...
 Said this, I'm not against the new getOverloads. On the 
 contrary, I find the feature cool. But if it is present, it 
 has to yield the same results for foo1 and foo2, just like the 
 pragmas.
In the general case, this is impossible. Even just limiting ourselves to simple usages of static if it gets unwieldy. This template is from Phobos (all unnecessary code removed). It has 224 different possible combinations of features: private struct _Cache(R, bool bidir) { static if (bidir) {} else {} static if (isInfinite!R) {} else {} static if (hasLength!R) {} version(assert) {} static if (isForwardRange!R) {} static if (hasSlicing!R) { static if (hasEndSlicing) {} static if (!isInfinite!R) {} else static if (hasEndSlicing) {} } } And that's before we even instantiate any templates that this template references - any templates used inside _Cache could increase the number of combinations. But wait, there's more! How many does this have? struct Bar(string s) { mixin(s); } If that's just one overload because of the impossibility of generating the options, what if we introduce a single static if inside it? Is that still one, or is it two? Do we count the possibilities introduced by a template mixin, but not by a string mixin? What if the template mixin comes from a template argument? Sorry if I come off as very dismissive right now - I kind of like the idea, but it seems impossible in practice.
 I'm not sure about the procedure... should I post this as a 
 question to the GitHub, so it can be discussed? pull 2351 or 
 8195?
What you are suggesting would be a fundamental change in the language, and should be discussed in the digitalmars.D forum. Either PR is not the right place to discuss it. If there's any interest, you will have to write a DIP for how the change will work. -- Simen
My point, is that if it is impossible to catch all cases of template rewriting (which I'm advocating from the beginning) getOverloads should not be extended to templates, as this would be a fundamental change in the language.
Apr 23 2018
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 23 April 2018 at 08:07:52 UTC, Alex wrote:
 On Monday, 23 April 2018 at 07:49:39 UTC, Simen Kjærås wrote:
 That's not the definition of lowering used elsewhere, and so 
 will lead to confusion and misunderstanding. I would strongly 
 suggest you rethink your definition of lowering.
There is no official definition. That's because some natural rewrite rules are implied, which are very general, I assume...
How official do you want it to be? That's the only definition in common use by others in the context of compilers.
 My point, is that if it is impossible to catch all cases of 
 template rewriting (which I'm advocating from the beginning) 
 getOverloads should not be extended to templates, as this would 
 be a fundamental change in the language.
There is no template rewriting taking place in the language today - that seems to be a feature you are arguing for. getOverloads should return the overloads of a template as they are defined in the language today. Template overloads are mentioned in the D spec[0], and are clearly a real thing that it's useful to be able to manipulate. You seem to be arguing against a feature on the basis that if the language were significantly different from what it is, the feature would be confusing. The language isn't that way, so the feature isn't confusing in that way. Having getOverloads return template overloads solves a real issue right now, and would be useful even if your suggested change were implemented (though the behavior would be slightly different). If your suggested feature is impossible to implement, are you suggesting we simply throw our hands in the air and give up, instead of implementing a useful feature? -- Simen [0]: https://dlang.org/concepts.html
Apr 23 2018
parent reply Alex <sascha.orlov gmail.com> writes:
On Monday, 23 April 2018 at 10:57:59 UTC, Simen Kjærås wrote:
 There is no official definition. That's because some natural 
 rewrite rules are implied, which are very general, I assume...
How official do you want it to be? That's the only definition in common use by others in the context of compilers.
Stating the existence of rewriting rules is a complex thing. They is no place in the docu (which I could find until now) where they are defined. Therefore, I rely on something which is common. https://en.wikipedia.org/wiki/Natural_transformation We can discuss, whether a template is a functor, but I think this would go beyond the scope of this thread. You mentioned a limited set of lowerings, defined by the language. Where can I find them? I'm very interested in that.
 There is no template rewriting taking place in the language 
 today - that seems to be a feature you are arguing for.
Semantic rewriting is not something which one can take control of. Either you have semantically equal constructs or not.
 getOverloads should return the overloads of a template as they 
 are defined in the language today.
This is the topic of discussion. I argue, that getOverloads can (so far) act only on functions, because only there the term overload is well defined. I see, that it could be defined on templates too, but not in the way you do.
 Template overloads are mentioned in the D spec[0], and are 
 clearly a real thing that it's useful to be able to manipulate.
Yes. But using the term "overload" for templates with the same name does not justify to wrongly apply existing features to more abstract objects. This is what you try to do by using getOverloads with templates.
 You seem to be arguing against a feature on the basis that if 
 the language were significantly different from what it is, the 
 feature would be confusing. The language isn't that way, so the 
 feature isn't confusing in that way.
Well, as we both saw with the pragma output, the language is exactly that way I'm describing it. New getOverload functionality would introduce a new constraint in comparison to the current state.
 Having getOverloads return template overloads solves a real 
 issue right now, and would be useful even if your suggested 
 change were implemented (though the behavior would be slightly 
 different).
What I'm saying is: it would be useful only then, if my suggested change were implemented. Otherwise, it is a new constraint and not a feature.
 [0]: https://dlang.org/concepts.html
Apr 23 2018
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 23 April 2018 at 13:32:49 UTC, Alex wrote:
 On Monday, 23 April 2018 at 10:57:59 UTC, Simen Kjærås wrote:
 There is no official definition. That's because some natural 
 rewrite rules are implied, which are very general, I assume...
How official do you want it to be? That's the only definition in common use by others in the context of compilers.
Stating the existence of rewriting rules is a complex thing. They is no place in the docu (which I could find until now) where they are defined. Therefore, I rely on something which is common. https://en.wikipedia.org/wiki/Natural_transformation We can discuss, whether a template is a functor, but I think this would go beyond the scope of this thread. You mentioned a limited set of lowerings, defined by the language. Where can I find them? I'm very interested in that.
As with all things D, the only real spec is the compiler source code. :p :( Again, lowerings are a compiler technique where one syntax is rewritten into a different syntax. They are hard-coded into the language (and optionally in the compiler - if a compiler writer finds an opportunity to use lowerings to simplify or otherwise improve the compiler without such a lowering being defined in the language, and with the same behavior, all the more power to him/her). Only these things are called lowerings. Template instantiations are not lowerings. Function inlining is not a lowering. Constant folding is not a lowering. As far as I know, there is no canonical list of lowerings anywhere. :(
 There is no template rewriting taking place in the language 
 today - that seems to be a feature you are arguing for.
Semantic rewriting is not something which one can take control of. Either you have semantically equal constructs or not.
And we don't, so don't worry about it. :p
 getOverloads should return the overloads of a template as they 
 are defined in the language today.
This is the topic of discussion. I argue, that getOverloads can (so far) act only on functions, because only there the term overload is well defined. I see, that it could be defined on templates too, but not in the way you do.
But why not? Consider this case: template foo() {} template foo(int n) {} In the compiler, this results in two `TemplateDeclaration`s being instantiated. Since they share the 'foo' name, they are considered an overload set by the compiler. The specific overload is chosen by overload resolution. They are iterated by the function overloadApply. The term template overload is also widely used in C++, from which D has inherited much. Simply put, template overloads are clearly defined, everyone knows what they are, and many of us want a way to iterate over them, choose a specific overload in a different template, and just plain do sensible work with them. If not how I define it, then how would the term 'overload' be defined for templates? We've established that creating a new overload for every potential static if breaks down in the presence of mixins, and is questionable in their absence. What I'm reading from your post is simply 'the current way is wrong, and there exists a better way'. If that's true, I'd love to hear what the better way is, but I fail to see that you've described it.
 Template overloads are mentioned in the D spec[0], and are 
 clearly a real thing that it's useful to be able to manipulate.
Yes. But using the term "overload" for templates with the same name does not justify to wrongly apply existing features to more abstract objects. This is what you try to do by using getOverloads with templates.
It's a term with decades of history in C++, and has been used in functions or types with different constraints.
 You seem to be arguing against a feature on the basis that if 
 the language were significantly different from what it is, the 
 feature would be confusing. The language isn't that way, so 
 the feature isn't confusing in that way.
Well, as we both saw with the pragma output, the language is exactly that way I'm describing it.
Then, once more, I must have misunderstood your description. Proving that two templates are equivalent is in general impossible, since any amount of wasted computation could be performed before the end result is returned, and inputs must be tested exhaustively for the proof to be valid. The fact that two templates give the same result in one special case does not mean that they are equivalent in the general case, and the compiler needs to care about the general case.
 New getOverload functionality would introduce a new constraint 
 in comparison to the current state.
The new functionality does not in any way stop you from using getOverloads in exactly the way it's been used before. In addition to that, you may pass an additional argument to it to get template overloads (actually, every symbol by that name in the specified aggregate). How is this in any way a constraint?
 Having getOverloads return template overloads solves a real 
 issue right now, and would be useful even if your suggested 
 change were implemented (though the behavior would be slightly 
 different).
What I'm saying is: it would be useful only then, if my suggested change were implemented. Otherwise, it is a new constraint and not a feature.
That's just plain false. I have use cases for the feature right now, so it's provably useful. Can you describe in which ways your suggested change is superior, rather than simply asserting that it is? -- Simen
Apr 23 2018
parent reply Alex <sascha.orlov gmail.com> writes:
On Monday, 23 April 2018 at 14:22:13 UTC, Simen Kjærås wrote:
 As with all things D, the only real spec is the compiler source 
 code. :p :(
:p
 Proving that two templates are equivalent is in general 
 impossible, since any amount of wasted computation could be 
 performed before the end result is returned, and inputs must be 
 tested exhaustively for the proof to be valid. The fact that 
 two templates give the same result in one special case does not 
 mean that they are equivalent in the general case, and the 
 compiler needs to care about the general case.
Ok, thats exactly the point. If you have functions void foo() {} void foo(int n) {} There is no ambiguity which function will be chosen if it will be called. If you have templates // form 1 template Foo(int N) if (N & 1) {} // A template Foo(int N) if (!(N & 1)) {} // B OR // form 2 template foo(int N) { static if(N & 1){} // A else{} // B } There is also no ambiguity which will be called. However, getOverloads will behave differently. This is not bad at all. But you have to admit, that while now, there is no way to distinguish form 1 and form 2, with the new getOverloads there will be. This seems strange to me, because there is no reason to distinguish form 1 and form 2. (Because the callable code, which will be generated is the same, I hope... ?) So, in particular, I'm not against the feature. And if the equivalence between form 1 and form 2 is gone, so what. But I don't understand the reasoning why something which is now equal won't be equal any more later?
Apr 23 2018
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 23 April 2018 at 15:00:38 UTC, Alex wrote:
 Ok, thats exactly the point. If you have functions

 void foo() {}
 void foo(int n) {}

 There is no ambiguity which function will be chosen if it will 
 be called.

 If you have templates

 // form 1
 template Foo(int N) if (N & 1)    {} // A
 template Foo(int N) if (!(N & 1)) {} // B

 OR

 // form 2
 template foo(int N)
 {
 	static if(N & 1){} // A
 	else{} // B
 }

 There is also no ambiguity which will be called.

 However, getOverloads will behave differently.
Now I understand what you are getting at. Thanks a bunch for being so patient with me. :) And you are right, of course. But I'm just as interested in the templates themselves as the instantiated results.
 This is not bad at all. But you have to admit, that while now, 
 there is no way to distinguish form 1 and form 2, with the new 
 getOverloads there will be.
 This seems strange to me, because there is no reason to 
 distinguish form 1 and form 2. (Because the callable code, 
 which will be generated is the same, I hope... ?)

 So, in particular, I'm not against the feature. And if the 
 equivalence between form 1 and form 2 is gone, so what. But I 
 don't understand the reasoning why something which is now equal 
 won't be equal any more later?
Ah, but I'm not looking to instantiate the templates, but to learn about them - how many parameters do they take? Are their UDAs different, so that I should warn the programmer? Must I wrap them in different ways? I hope we now both understand what the other person means and want to achieve. :) -- Simen
Apr 23 2018
parent Alex <sascha.orlov gmail.com> writes:
On Monday, 23 April 2018 at 15:44:10 UTC, Simen Kjærås wrote:
 Ah, but I'm not looking to instantiate the templates, but to 
 learn about them - how many parameters do they take? Are their 
 UDAs different, so that I should warn the programmer? Must I 
 wrap them in different ways?
So... Do I have to correct my semantic equality assumptions? There should be a large chapter in the docu about this. It would change at the same time with your feature and the things which are implied and which are not would be evident to the world...
Apr 23 2018
prev sibling parent reply Arafel <er.krali gmail.com> writes:
I think both versions are not equivalent at all. Consider [1]:

```
import std.meta;

void main()
{
     pragma(msg, __traits(getMember, A, "Foo1").stringof); // Foo1(int 
N) if (N & 1)
     pragma(msg, __traits(getAttributes, __traits(getMember, A, 
"Foo1"))[0]); // tuple("int", "odd")
     alias f1a = Instantiate!(__traits(getMember, A, "Foo1"), 1); // 
This is expected
     pragma(msg, f1a); // A
     alias f1b = Instantiate!(__traits(getMember, A, "Foo1"), "+"); // 
Why would I know that I can even instantiate?? Also, can I haz UDA plz?
     pragma(msg, f1b); // B
}

class A {
      ("int", "odd")
	template Foo1(int N) if (N & 1)    {
         enum Foo1 = "A";
     }
      ("string", "+")
	template Foo1(string op) if (op == "+") {
         enum Foo1 = "B";
     }
}
```

In this case you could perhaps use an alias parameter to achieve a 
similar effect. I haven't tried it but it would be really messy, if it 
even works.

What seems clear to me from this case is that *internally* the compiler 
sees a *set* of "overloads" (change that word if you think it should 
only apply to functions), but I can only get the first of the batch!

For example, I might want to add some information in the UDA on how to 
instantiate the template, but then I can't get the UDA either. I'm sure 
it's somewhere, just that we get no access to it, and that shouldn't be 
too hard to add.

I think this clarifies a bit the problem I see.

A.

[1]: https://run.dlang.io/is/zRDHGn

On 04/23/2018 05:00 PM, Alex wrote:
 On Monday, 23 April 2018 at 14:22:13 UTC, Simen Kjærås wrote:
 As with all things D, the only real spec is the compiler source code. 
 :p :(
:p
 Proving that two templates are equivalent is in general impossible, 
 since any amount of wasted computation could be performed before the 
 end result is returned, and inputs must be tested exhaustively for the 
 proof to be valid. The fact that two templates give the same result in 
 one special case does not mean that they are equivalent in the general 
 case, and the compiler needs to care about the general case.
Ok, thats exactly the point. If you have functions void foo() {} void foo(int n) {} There is no ambiguity which function will be chosen if it will be called. If you have templates // form 1 template Foo(int N) if (N & 1)    {} // A template Foo(int N) if (!(N & 1)) {} // B OR // form 2 template foo(int N) {     static if(N & 1){} // A     else{} // B } There is also no ambiguity which will be called. However, getOverloads will behave differently. This is not bad at all. But you have to admit, that while now, there is no way to distinguish form 1 and form 2, with the new getOverloads there will be. This seems strange to me, because there is no reason to distinguish form 1 and form 2. (Because the callable code, which will be generated is the same, I hope... ?) So, in particular, I'm not against the feature. And if the equivalence between form 1 and form 2 is gone, so what. But I don't understand the reasoning why something which is now equal won't be equal any more later?
Apr 23 2018
parent reply Alex <sascha.orlov gmail.com> writes:
On Monday, 23 April 2018 at 16:16:09 UTC, Arafel wrote:
 ```
 import std.meta;

 void main()
 {
     pragma(msg, __traits(getMember, A, "Foo1").stringof); // 
 Foo1(int N) if (N & 1)
     pragma(msg, __traits(getAttributes, __traits(getMember, A, 
 "Foo1"))[0]); // tuple("int", "odd")
     alias f1a = Instantiate!(__traits(getMember, A, "Foo1"), 
 1); // This is expected
     pragma(msg, f1a); // A
     alias f1b = Instantiate!(__traits(getMember, A, "Foo1"), 
 "+"); // Why would I know that I can even instantiate?? Also, 
 can I haz UDA plz?
     pragma(msg, f1b); // B
 }

 class A {
      ("int", "odd")
 	template Foo1(int N) if (N & 1)    {
         enum Foo1 = "A";
     }
      ("string", "+")
 	template Foo1(string op) if (op == "+") {
         enum Foo1 = "B";
     }
 }
 ```
I'm not arguing about the case of different interfaces. It is more or less ok, as from different argument types it will be unambiguous which template will be instantiated. It is the case of differentiating templates by their structure and/or constraints. In this case, it is almost sure, that more then one form of implementation exists. However, the forms will yield the same semantic result. And I'm wondering why the implementation form alone leads to differentiation.
Apr 23 2018
parent reply Arafel <er.krali gmail.com> writes:
On Monday, 23 April 2018 at 16:52:11 UTC, Alex wrote:
 On Monday, 23 April 2018 at 16:16:09 UTC, Arafel wrote:
 ```
 import std.meta;

 void main()
 {
     pragma(msg, __traits(getMember, A, "Foo1").stringof); // 
 Foo1(int N) if (N & 1)
     pragma(msg, __traits(getAttributes, __traits(getMember, A, 
 "Foo1"))[0]); // tuple("int", "odd")
     alias f1a = Instantiate!(__traits(getMember, A, "Foo1"), 
 1); // This is expected
     pragma(msg, f1a); // A
     alias f1b = Instantiate!(__traits(getMember, A, "Foo1"), 
 "+"); // Why would I know that I can even instantiate?? Also, 
 can I haz UDA plz?
     pragma(msg, f1b); // B
 }

 class A {
      ("int", "odd")
 	template Foo1(int N) if (N & 1)    {
         enum Foo1 = "A";
     }
      ("string", "+")
 	template Foo1(string op) if (op == "+") {
         enum Foo1 = "B";
     }
 }
 ```
I'm not arguing about the case of different interfaces. It is more or less ok, as from different argument types it will be unambiguous which template will be instantiated. It is the case of differentiating templates by their structure and/or constraints. In this case, it is almost sure, that more then one form of implementation exists. However, the forms will yield the same semantic result. And I'm wondering why the implementation form alone leads to differentiation.
Well, with templates the overload resolution must be always unambiguous: ``` import std.stdio; void main() { pragma(msg, A.Foo1!2); pragma(msg, A.Foo1!3); static assert(!is (typeof(A.Foo1!6))); // Compilation failure if there is any ambiguity } class A { template Foo1(int N) if ((N % 2) == 0) { enum Foo1 = "A"; } template Foo1(int N) if ((N % 3) == 0) { enum Foo1 = "B"; } } ``` Also, you can try without a constraint, it will still complain. But you are arguing from the point of view of a hypothetical semantical equivalence that I don't think it's so clear. Both are tools that in some cases can lead to the same result, but there are also cases where they don't math. You could also argue that function overloads are just semantically equivalent to a single function with variadic arguments. Whether the compiler actually lowers it like that or not should be just an implementation detail, and thus simply not relevant. And from a syntactical point of view, it wouldn't make any sense if the following "overloads" were treated differently: ``` class A { ("int", "odd") template Foo1(int N) if (N & 1) { enum Foo1 = "A"; } ("int", "even") template Foo1(int N) if (!(N & 1)) { enum Foo1 = "B"; } ("string", "+") template Foo1(string op) if (op == "+") { enum Foo1 = "C"; } ("multi", "string") template Foo1(T...) if (allSatisfy!(isSomeString, typeof(T)) && T.length > 1) { enum Foo1 = "D"; } ("multi", "double") template Foo1(T...) if (allSatisfy!(isFloatingPoint, typeof(T)) && T.length > 1) { enum Foo1 = "E"; } } ``` How would you know which ones are "real" overloads (in your meaning)? A.
Apr 23 2018
parent Alex <sascha.orlov gmail.com> writes:
On Monday, 23 April 2018 at 17:46:10 UTC, Arafel wrote:
 You could also argue that function overloads are just 
 semantically equivalent to a single function with variadic 
 arguments.
It is not. As there are exact known, distinct, finite numbers and types of arguments of functions, which can be used. For example, zero and one. So: void foo(){} void foo(int){} How this is achievable with variadic functions?
 Whether the compiler actually lowers it like that or not should 
 be just an implementation detail, and thus simply not relevant.
Right.
 And from a syntactical point of view, it wouldn't make any 
 sense if the following "overloads" were treated differently:

 ```
 class A {
      ("int", "odd")
 	template Foo1(int N) if (N & 1)    {
         enum Foo1 = "A";
     }
      ("int", "even")
 	template Foo1(int N) if (!(N & 1))    {
         enum Foo1 = "B";
     }
      ("string", "+")
 	template Foo1(string op) if (op == "+") {
         enum Foo1 = "C";
     }
      ("multi", "string")
 	template Foo1(T...) if (allSatisfy!(isSomeString, typeof(T)) 
 && T.length > 1) {
         enum Foo1 = "D";
     }
      ("multi", "double")
 	template Foo1(T...) if (allSatisfy!(isFloatingPoint, 
 typeof(T)) && T.length > 1) {
         enum Foo1 = "E";
     }
 }
 ```
So, in my opinion, a callable object is overloaded, when there exist more then one interface how to call it. Before a template is instantiated, no callable objects exist. Now, you can go on say well, a template defines a family of objects, which build up an overload set. This is not doable, see the discussion before. But of course, you can go the other way and say well, if there exist a declaration then, based on the declaration inspection, let us define an overload set, based on the same symbol names. This is a different way of defining an overload, which is not obvious, especially if you see the pragma output from a former post. Therein the pragmas, which act also before runtime do not differ between declarations, but only between instantiations.
Apr 23 2018
prev sibling parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Thursday, 19 April 2018 at 13:57:04 UTC, Simen Kjærås wrote:
 On Tuesday, 17 April 2018 at 14:22:27 UTC, Arafel wrote:
 Hi!

 Is there any way to get the full set of templates that are 
 "overloaded" (in my case, based on constraints)?
Currently, there is no way (that I've found, at least) to do this. If you have a workaround, that's great, but there really should be a way - probably __traits(getOverloads). Having __traits(getOverloads) return templates as well should fix some of the issues __traits(getOverloads) has, as a bonus.
https://github.com/dlang/dmd/pull/8195 -- Simen
Apr 19 2018