Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue 16486: Alias template function parameter resolution #9778

Closed
wants to merge 9 commits into from
Closed

Fix issue 16486: Alias template function parameter resolution #9778

wants to merge 9 commits into from

Conversation

baziotis
Copy link
Contributor

@baziotis baziotis commented May 12, 2019

This is a 3-day prototype implementation of this DIP PR: dlang/DIPs#156
More comments / info on the implementation are in this article: http://users.uoa.gr/~sdi1600105/dlang/alias.html

So, if a parameter is a template alias, we iteratively (because a template alias might be alias to another template alias) reach the actual type of the parameter before proceeding into any type-checking.

The main idea is:

while(paramType is ad = aliasTemplateDeclaration) {
    adtype = ad.getType();
    if(adtype is TypeInstance) {
        paramType = aliasTemplateDeclaration
    }
}

It fails (at least) in such cases:

struct TestType(T) {}
alias TestAlias(T, Q) = TestType!(T);
void testFunction(T, Q)(TestAlias!(T, Q) arg) {}

void main()
{
    TestAlias!(int, float) testObj;
    testFunction(testObj);
}

This basically resolves to this:

struct TestType(T) {}
void testFunction(T, Q)(TestType!T arg) {}

This code breaks and rightly so, independently of the bug. However, I'm not sure whether the initial code should break.

@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @baziotis! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the annotated coverage diff directly on GitHub with CodeCov's browser extension
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

Please see CONTRIBUTING.md for more information.


If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.

Bugzilla references

Auto-close Bugzilla Severity Description
16486 enhancement Compiler see template alias like a separate type in template function definition

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub fetch digger
dub run digger -- build "master + dmd#9778"

Copy link
Contributor

@thewilsonator thewilsonator left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise looks great!

//printf("prmtype: %s\n", prmtype.toChars());
while (prmtype.ty == Tinstance)
{
TypeInstance prmti = cast(TypeInstance) prmtype;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can probably do while (TypeInstance prmti = prmtype.isInstance()) (if isInstance() doesn't exist please create it) similar to the if (auto td = prmti.tempinst.tempdecl.isTemplateDeclaration()) below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is the isTemplateInstance but that is for symbols. If I understand it correctly, code will look kind of weird if I introduce that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isTypeInstance()?

inout(TypeInstance) isTypeInstance() { return ty == Tinstance ? cast(typeof(return))this : null; }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I needed, thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thats what it is. Sorry about that, I'm going to blame travelling and jet lag for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't mention it. It's odd though, because I saw the idiom I used in a lot of places in the compiler and not once the isTypeInstance(). It might be a recent addition...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still want that? Because I think it can't happen because it is a while and not an if.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes:

if (TypeInstance prmti = prmtype. isTypeInstance())
{
    Objects* tempargs = prmti.tempinst.tiargs;
    while ((prmti = prmtype. isTypeInstance()))
    {
        //...
    }
}

src/dmd/dtemplate.d Outdated Show resolved Hide resolved
@baziotis
Copy link
Contributor Author

baziotis commented May 13, 2019

Thanks Nicholas for the review, but unfortunately those are minor details compared to the current problem.

I didn't have much time today, but I'll try again tomorrow. Here are some problems in case anyone has any suggestion.

The one problem is that we need to handle cyclic template declarations. One example of that is FormatSpec. Basically, a goes to b and back to a. Currently, I solve this with a hack, keeping the previous type and checking if the next is the same. It is just that, in the general case, we probably need a full DFS cycle detection.

But that's not the most important problem. This findTempDecl() there, changes the properties of the object (prmti.tempinst) it's called upon. It changes (at least) the tempinst.tempdecl to the declaration found (I suppose there's a reason that it doesn't just return the declaration).
The result is that if for any reason we want to roll-back to the initial type, I didn't find a way to do it. I tried resetting tempdecl to null, using .copy() on prmti, even manual mem.xmalloc() on prmti.tempinst and memcpy.
Just for clarification, the fact that we can't roll-back to the initial type causes future checks to fail.

@baziotis
Copy link
Contributor Author

I want to stress that I did not pay much attention to the quality of the code.
There are 2 main reasons for that:

  1. The first important thing is that you like the idea for the fix, which again, is going towards a DIP.
  2. The second is to be sure that the implementation approach works for the project, before I spend any time to make it nicer. To that end, it is important that there is a review / comment about how I went to solve the 2 problems addressed above.

@wilzbach
Copy link
Member

Merge branch 'fix_alias' of https://github.com/baziotis/dmd

Please do rebases instead as this will keep the commit history clean.

I want to stress that I did not pay much attention to the quality of the code.

We're aware, but the better habit is to things correctly from the start ;-)
Also, whenever you're just experimenting with code and checking how the CIs will react, use GitHub's draft PR feature or "[WIP]" in the title to indicate to reviewers that they shouldn't look too closely yet.

@baziotis
Copy link
Contributor Author

baziotis commented May 13, 2019

Please do rebases instead as this will keep the commit history clean.

Good to know.

We're aware, but the better habit is to things correctly from the start ;-)

Of course, It's not that I didn't take the time to make it better out of boredom, I will happily do it. But I thought that there's no point to restructure it if the approaches used are not desirable.

Also, whenever you're just experimenting with code and checking how the CIs will react, use GitHub's draft PR feature or "[WIP]" in the title to indicate to reviewers that they shouldn't look too closely yet.

I should have used the draft PRs for the previous commits indeed, I didn't know it. But at this point, I believe it is the time for someone to look closer.

@@ -0,0 +1,36 @@
// Example 1 - Simple case
struct TestType(T, Q) { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following doesn't work.

Suggested change
struct TestType(T, Q) { }
struct TestType(A, B) { }
alias TestAliasA(A, B) = TestType!(A, B);
alias TestAlias(T, Q) = TestAliasA!(T, Q);

Maybe you can replace the identifiers.

Copy link
Member

@SSoulaimane SSoulaimane May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither does this.

Suggested change
struct TestType(T, Q) { }
struct TestType(T, Q) { }
template TestAliasB(T, Q)
{
alias A = T;
alias TestAliasB = TestType!(A, Q);
}
alias TestAlias(T, Q) = TestAliasB!(T, Q);

alias A = T should be resolved.

Copy link
Contributor Author

@baziotis baziotis May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second is still a little obscure to me. I'll try to understand it again.

For the first one, I understand why it makes sense to work. But, trying to implement it, here are the consequences. At some point, we reach the declaration TestAliasA(A, B) and this has type TestType!(A, B). This I think is like an incomplete type. Incomplete because of the A, B, which are identifiers, and their resolution depends on the scope. This is resolved depending on where it's used.
It seems I could replace A, B with whatever were the initial parameters. The problem with that is that we have now changed the type of the declaration TestAliasA. Code-wise, the consequence of that is that if we reach a template and its argument names don't match exactly the initial ones, we have to create a new type. I think that no matter how many alias indirections we want, we can just make a copy only in the last (the one that resolves to a non-alias template instance).
Is that acceptable?

Copy link
Contributor Author

@baziotis baziotis May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it seems that both pass. That was too easy to be correct though...
Just a note: It makes sense only if the num of root parameters and final parameters is the same. Not that it is difficult to take it into account, but if they're not the same, there is a more difficult problem anyway. The one on the description.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too simplistic. This doesn't wrok:

Suggested change
struct TestType(T, Q) { }
alias TestAlias(T, Q) = TestAliasA!(Q, T);

You should follow the parameters throughout, don't count on the order.

Copy link
Contributor Author

@baziotis baziotis May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. I realized, after the commit, by testing something like this, that some "following cleverness" should be added. Thanks though! Actually, for such parameters it is not difficult. But I didn't update it since then I thought about cases like:

struct TestType(A, B) { }
alias TestAlias(A: A*, B) = TestType!(A*, B);
alias TestAlias2(T, Q) = TestAlias!(T*, Q);

Something like that. Here, A* is different from T (the initial argument) and so it will be replaced by T.
Meaning, parameters are not just identifiers. Still, it seems feasible because the ways that you can pass template parameters, however complicated they might be, all "look" like a declaration (they may even be parsed like that, I haven't looked). That is, they have only one identifier and that is the only you need to find and replace. But I better look at it tomorrow with a clear head.
Lastly, the follow-through, as said before, at least as I'm thinking it now, requires copies all the way.

// and 'b' goes back to 'a'. For now, we save the previous type
// and test it against (if they're equal) the new type.
// TODO: In the general
// case, we probably need full DFS cycle detection.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One common "trick/hack/easy solution" in other parts of DMD is to use an iteration counter with a cut-off (e.g. 1000).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or he can use the inuse field

int inuse; // used to detect cycles

int inuse; /// for recursive expansion detection

int inuse; // for recursive expansion detection

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, those can come in handy. It also seems that the problem does not require full cycle detection because there's always only one way to go, meaning it's like floyd's tortoise and hare. I don't know if this is going to be useful though..

@WalterBright
Copy link
Member

This needs a DIP to clearly spell out how the changes to template matching / instantiation.

@thewilsonator thewilsonator added the Review:Needs Spec PR A PR updating the language specification needs to be submitted to dlang.org label May 15, 2019
Type savetype = prmtype;
Type prevtype = null;
//printf("prmtype: %s\n", prmtype.toChars());
if(prmtype.ty == Tinstance) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ on new line.

@baziotis
Copy link
Contributor Author

This needs a DIP to clearly spell out how the changes to template matching / instantiation.

Thank you, it's in progress, still on draft version.

@baziotis
Copy link
Contributor Author

I'm postponing this PR for now. Your feedback, a discussion with one of my professors and me
reading for hours specs, type-systems and the DMD source code made obvious that to support this feature to its fullest extent, possibly a lot of code and time should be spent. Unfortunately, right now I don't have that time.
I'm currently writing a DIP to specify the desired behavior as good as I can. When it's ready, I will update with a comment. I'm leaving the PR open as a prototype version because after all, it mostly implements the initial needs. Someone might find use of it. If anyone goes to do full implementation, please keep me posted.
Last but not least, you might like this article, in which I describe the most important points I found as I was working in this.

@baziotis
Copy link
Contributor Author

baziotis commented May 17, 2019

Here is a first somewhat complete try on the DIP: https://github.com/baziotis/DIPs/blob/master/Drafts/1NNN-SB.md

Anyone's feedback is greatly appreciated. @WalterBright and @andralex your input is especially valuable.

@atilaneves
Copy link
Contributor

@baziotis The DIP link is broken.

@baziotis
Copy link
Contributor Author

Fixed thanks!

@atilaneves
Copy link
Contributor

In the example motivating the DIP:

auto foo(T)(Slice!(StairsIterator!(T*, "-")) m)
{
}

alias PackedUpperTriangularMatrix(T) = Slice!(StairsIterator!(T*, "-"));

// fails, issue 16486
auto foo(T)(PackedUpperTriangularMatrix!T m) { }

Why wouldn't this be sufficient?

enum isPackedUpperTriangularMatrix(T) = is(T: Slice!(StairsIterator!(U*, "-")), U);
auto foo(T)(T m) if(isPackedUpperTriangularMatrix!T) { /* ... */ }

@9il
Copy link
Member

9il commented May 27, 2019

Why wouldn't this be sufficient?

Because it is much more complex for real-world API.

void composeLU(T)(
    PackedLowerTriangularMatrix!(const T) l, // First Slice type for Lower Matrix
    PackedUpperTriangularMatrix!(const T) u, // Second Slice type for Lower Matrix
    BlasMatrix!T lu, // Third Slice type for General Matrix
)
    if (is(T == float) && is(T == double))
{
    ...
}

A workaround without resolution looks ugly, requires much more code, and very unreadable.
Also, the error msg the workaround prints is useless, with resolution user can write composeLU!double and get very well message. All mir-* code never uses aliases and workarounds for public API.

@atilaneves
Copy link
Contributor

I don't see how this is so much more complicated:

void composeLU(L, U, LU)(L l, U u, LU lu)
    if(IsPackedLowerTriangularMatrix!L 
       && isPackedUpperTriangularMatrix!U
       && isBlasMatrix!LU
       && /* .. */
    )
{
}

@9il
Copy link
Member

9il commented May 27, 2019

I don't see how this is so much more complicated:

void composeLU(L, U, LU)(L l, U u, LU lu)
    if(IsPackedLowerTriangularMatrix!L 
       && isPackedUpperTriangularMatrix!U
       && isBlasMatrix!LU
       && /* .. */
    )
{
}

The devil in the details, the && /* .. */. Your message is the best explanation of the issue, users are too lazy to write that && /* .. */, the stuff you prefer to miss. The good API mustn't require this kind of ****. Just try to write the skipped conditions. To be clear: they check that l, u, and lu composed of floating point elements of the same type and l and u has const elements, andlu has mutable elements.

EDIT: plus they skipped conditions check that the floating point type is constrained to float and double.

EDIT2: in addition, the user code very likely needs to get this floating point type to perform other operations. So, the user needs to add a further statement in the function body.

@thewilsonator
Copy link
Contributor

and then consider the error message you get if that goes ever so slightly wrong (for both users trying to write such constraints and for users (mis)using such a function).

@atilaneves
Copy link
Contributor

The devil in the details

Yes, and the devil is similarly in the details of the guts of the type aliases.

Your message is the best explanation of the issue, users are too lazy

Would it be acceptable to leave out the template constraints? In most cases I see in the wild, templates are over-constrained anyway (especially if checking for a particular type as is the case in the examples here, instead of what the type can actually do).

I'm confused at what kind of user would be able to correctly write a type alias but not a template constraint.

The DIP seems to me like a weaker version of Rust's traits or Haskell's typeclasses, so why accept a weaker version if the language is going to be changed anyway?

@atilaneves
Copy link
Contributor

and then consider the error message you get if that goes ever so slightly wrong (for both users trying to write such constraints and for users (mis)using such a function).

I wholeheartedly agree that getting better error messages that guide the user should be a goal. However, I see no discussion on how the DIP would ameliorate the current situation.

@9il
Copy link
Member

9il commented May 27, 2019

Would it be acceptable to leave out the template constraints? In most cases I see in the wild, templates are over-constrained anyway (especially if checking for a particular type as is the case in the examples here, instead of what the type can actually do).

No. It is not acceptable for numeric and data libraries in most of the cases. For example, a function can use precompiled D and/or C kernels or call the functions that have stronger constraints.

I'm confused at what kind of user would be able to correctly write a type alias but not a template constraint.

You didn't write the correct version, it was incomplete. I don't want my libraries to test users abilities to write constraints. I want libraries that help users.

The DIP seems to me like a weaker version of Rust's traits or Haskell's typeclasses, so why accept a weaker version if the language is going to be changed anyway?

Well, this is the news for me (from dconf?). Is it D3? Who, when, how will change the language? Where the roadmap and new features descriptions can be found?

and then consider the error message you get if that goes ever so slightly wrong (for both users trying to write such constraints and for users (mis)using such a function).

I wholeheartedly agree that getting better error messages that guide the user should be a goal. However, I see no discussion on how the DIP would ameliorate the current situation.

I don't know about what situation you are writing. Looks like you missed the point I wrote above.

@atilaneves
Copy link
Contributor

No. It is not acceptable for numeric and data libraries in most of the cases.

What would happen if the contraints weren't there?

You didn't write the correct version, it was incomplete

I focussed on the template contraints that were the subject of the PR and DIP. I wrongfully assumed the rest was implied.

Well, this is the news for me (from dconf?). Is it D3?

I meant: why write a DIP for a weaker version of traits? Especially since, unless I'm missing something, it's just syntax sugar (and not a whole lot) for what can be done with template constraints currently?

I don't know about what situation you are writing. Looks like you missed the point I wrote above.

I'm writing about better error messages, since that's what @thewilsonator mentioned.

@9il
Copy link
Member

9il commented May 27, 2019

No. It is not acceptable for numeric and data libraries in most of the cases.

What would happen if the contraints weren't there?

It may not compile with a useless error message.

Or, it may compile and produce the wrong numeric code.

Or, it may break the API completely because other functions overload the same name.

The constraints are required because of the same reasons Phobos requires them, for the numeric code they are even more sensible.

For simple algorithms like each, reduce this does not really matter, even a bad error msg may be good enough to understand what's wrong. However, for creepy numeric algorithms, these errors will just make a user move his work to old good C/Fortran, not a bad python, or promising Julia.

You didn't write the correct version, it was incomplete

I focussed on the template contraints that were the subject of the PR and DIP. I wrongfully assumed the rest was implied.

Well, this is the news for me (from dconf?). Is it D3?

I meant: why write a DIP for a weaker version of traits? Especially since, unless I'm missing something, it's just syntax sugar (and not a whole lot) for what can be done with template constraints currently?

Because of the complexity of API without this feature. The constraint will look too large.

I don't know about what situation you are writing. Looks like you missed the point I wrote above.

I'm writing about better error messages, since that's what @thewilsonator mentioned.

The end of #9778 (comment):

Also, the error msg the workaround prints is useless, with resolution user can write composeLU!double and get very well message. All mir-* code never uses aliases and workarounds for public API.

... maybe except some API inherited from Phobos.

@atilaneves
Copy link
Contributor

Because of the complexity of API without this feature. The constraint will look too large.

I don't see much of a difference between large template constraints and large parameter lists. The example given above looks nearly identical to me in either form.

@9il
Copy link
Member

9il commented May 27, 2019

Because of the complexity of API without this feature. The constraint will look too large.

I don't see much of a difference between large template constraints and large parameter lists. The example given above looks nearly identical to me in either form.

OK. I don't force you to change your vision.

@baziotis
Copy link
Contributor Author

baziotis commented May 27, 2019

@atilaneves Thanks for feedback. I agree with Ilya on this discussion. Generally, it seems to me that with this feature, we can have more readable code than the alternatives.
Let me comment on this though:

I meant: why write a DIP for a weaker version of traits? Especially since, unless I'm missing something, it's just syntax sugar (and not a whole lot) for what can be done with template constraints currently?

  1. For me, this is more syntactic sugar than a new feature. But, it's syntactic sugar that:
    • Should exist in the language. Personally, when I was a newcomer, it didn't make any sense why
      template aliases as parameters can't be used (it still doesn't).
    • For "just syntax sugar" you might think that the DIP is way too complicated. But note that there's
      no prior work in D to explain formally how templates are resolved (at least, not that I know of). And so,
      describing it formally is not that simple.
  2. I'm not aware of Rust traits. I'm a little bit aware of Haskell typeclasses. I don't think there is any significant relationship. Template aliases in D are not the equivalent of Haskell typeclasses. Or maybe I miss something.

@thewilsonator
Copy link
Contributor

it didn't make any sense why template aliases as parameters can't be used (it still doesn't).

Right, currently template aliases decay to the underlying type when doing type checking like all aliases do, however they really should behave more like a type definition for the purposes of matching (a typedef if you will).

It should behave like using in:

#include <vector>
template<typename T>
using vec = std::vector<T>;
void foo(vec<float>) {}
int main(int,char**)
{
    vec<float> a;
    foo(a);
}

@baziotis
Copy link
Contributor Author

Might have missed something, but as parameters in template functions, they don't "decay to the underlying type". That is the problem. I mean this is the (not good) exception.

Yes, my intention was too to work like using. Unfortunately, the C++ standard does not have much info about the resolution of using.

@thewilsonator
Copy link
Contributor

Unfortunately, the C++ standard does not have much info about the resolution of using.

Thats annoying.

@baziotis
Copy link
Contributor Author

Really, that's not specific to alias resolution or using. For example, the most complicated part of the DIP, the Gen function, has to do with template resolution. And for that I could also not find some explicit info on D spec or C++ spec.

@atilaneves
Copy link
Contributor

Since a workaround exists with template constraints, I continue to struggle to see the value in this. @WalterBright ?

@baziotis
Copy link
Contributor Author

baziotis commented Jun 3, 2019

I think me and @9il explained our opinions. You too, and thanks for the feedback.
But, unless someone has any new constructive feedback, we probably should wait for the standard procedure.
I CC'd Walter and Andrei above. I guess they don't give early feedback. I remember Andrei saying on the DConf that when Walter gave feedback early, the results were bad (the author abandoned the project).

@JinShil
Copy link
Contributor

JinShil commented Jun 3, 2019

@9il
Copy link
Member

9il commented Jun 4, 2019

@baziotis, @atilaneves is the new @andralex as of https://youtu.be/cpTAtiboIDs?t=3074

A rhetorical question: Why Atila, why not Seb?

@jacob-carlborg
Copy link
Contributor

Perhaps Atila volunteered. As far as I understand he's working for Symmetry now and has some dedicated time to do "Andrei work".

@baziotis
Copy link
Contributor Author

baziotis commented Jun 4, 2019

@baziotis, @atilaneves is the new @andralex as of https://youtu.be/cpTAtiboIDs?t=3074

Congratulations for that Atila! I didn't know it. Again, the same thing is true. If there's no new info from both parties, then we should wait for the standard procedure. But, if Atila is part of the final acceptance and he thinks is not a good idea, then we should hear from @WalterBright and maybe drop it sooner if it is to be dropped anyway?

@thewilsonator
Copy link
Contributor

I continue to struggle to see the value in this.

Because it is faaaaar easier to write and correctly use types, than constraints.
When they break because you use them wrong the blow up in your face less (although this is still a problem).

A template signature has three parts: compile time parameters, runtime parameters (which may depend on the compile time ones) and constraints (ditto).
Constraints to me are for specialising the cases of the properties of the compile time arguments, e.g. the different subtypes of ranges.

Since a workaround exists with template constraints,

You can write generic code in C, too. Just because you can work around it, doesn't mean there isn't a problem that would benefit from being solved.

@baziotis baziotis closed this Mar 6, 2020
@Lucipetus
Copy link
Contributor

This thread has been closed, but it seems to me this issue really needs fixing.

If I already have a well-defined AliasType, why do I have to write extra code to implement isAliasType?

I'd even be happy with the constraint solution if it's not so counter-intuitive to implement.

struct Matrix(U, size_t M, size_t N)
{
}

alias Vector(U, size_t N) = Matrix!(U, N, 1);

enum isVector(U) = is(U == Vector!(S, N), S, size_t N); //  I guess it fails for the same reason.

enum isVectorCorrect(U) = is(U == Matrix!(S, N, 1), S, size_t N); // the "correct" way and how much I like to REPEAT myself!

void foo(U)(Vector!(U, 3) a)
{
}

void bar(U)(U a) if (isVector!U)
{
}

void main()
{
    import std.stdio;

    Vector!(float, 3) v;
    foo(v); // Error: none of the overloads of template `app.doSomething` are callable using argument types `!()(Matrix!(float, 3LU, 1LU))
    bar(v); // failed constraint isVector!U
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Review:Needs Spec PR A PR updating the language specification needs to be submitted to dlang.org Severity:Enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.