Jump to page: 1 2
Thread overview
enums and std.traits
Aug 04, 2012
Jonathan M Davis
Aug 04, 2012
Timon Gehr
Aug 04, 2012
Jonathan M Davis
Aug 04, 2012
Jonathan M Davis
Aug 05, 2012
Christophe Travert
Aug 05, 2012
Jonathan M Davis
Aug 06, 2012
Christophe Travert
Aug 06, 2012
deadalnix
Aug 05, 2012
Adam D. Ruppe
Aug 05, 2012
Jonathan M Davis
Aug 06, 2012
deadalnix
August 04, 2012
At present (though it didn't used to work this way until about 4 months ago - the change is in 2.060), a number of std.traits templates - including isSomeString - evaluate to false for enums. So,

enum E : string { a = "hello" }
static assert(isSomeString!(E.a));

will fail. This is in spite of the fact that

auto func(string a) {...}

will accept E.a without complaint. So, if you change

auto func(string a) {...}

to

auto func(T)(T a) if(isSomeString!T) {...}

any code passing E.a to func would be broken. Personally, I don't see why isSomeString shouldn't consider enums which are strings to be strings (the same goes for isUnsigned, isIntegral, etc.), especially when the language itself does.

Kenji is argues that they shouldn't because they should treat enums as strongly typed:

https://github.com/D-Programming- Language/phobos/commit/52462bec6ea846f30e8dac309d63ccb4101e8f0c#commitcomment-1671599

But the language itself doesn't and I think that this a real problem given that it's very common to use stuff like isSomeString in template constraints, and I don't think that much of anyone is considering how that effects enums.

I agree that an enum's base type should not implicitly convert to the enum type, since that would mean that you could have an invalid enum value, but I see no problem with enums implicitly converted to their base type.

I think that the change to std.traits was a big mistake and will probably file it as a regression, but I thought that I should get other people's thoughts on this.

- Jonathan M Davis
August 04, 2012
On 08/04/2012 11:09 PM, Jonathan M Davis wrote:
> I agree that an enum's base type should not implicitly convert to the enum
> type, since that would mean that you could have an invalid enum value, but I
> see no problem with enums implicitly converted to their base type.
>

Well, there is a problem:

T fun(T)(T arg) if(isSomeString!arg){
    return arg~arg[0];
}

The constraint would be too weak.
August 04, 2012
On Sunday, August 05, 2012 00:15:02 Timon Gehr wrote:
> On 08/04/2012 11:09 PM, Jonathan M Davis wrote:
> > I agree that an enum's base type should not implicitly convert to the enum type, since that would mean that you could have an invalid enum value, but I see no problem with enums implicitly converted to their base type.
> Well, there is a problem:
> 
> T fun(T)(T arg) if(isSomeString!arg){
>      return arg~arg[0];
> }
> 
> The constraint would be too weak.

True, but that's still going to cause a lot fewer problems than making isSomeString fail for enums will, and if auto is used, then it's not a problem.

Though honestly, I'd argue that the compiler should treat arg ~ arg[0] as being string rather than the enum type anyway, since it makes no sense to get a new enum value by concatenating to an existing one. The new one probably isn't a valid enum value. It's the same problem that we get when we allow bit manipulation on enum types (which should also be disallowed IMHO). So, I think that that's more of a problem with enums than isSomeString.

- Jonathan M Davis
August 04, 2012
On Saturday, August 04, 2012 15:22:34 Jonathan M Davis wrote:
> On Sunday, August 05, 2012 00:15:02 Timon Gehr wrote:
> > T fun(T)(T arg) if(isSomeString!arg){
> > 
> >      return arg~arg[0];
> > 
> > }

> Though honestly, I'd argue that the compiler should treat arg ~ arg[0] as being string rather than the enum type anyway, since it makes no sense to get a new enum value by concatenating to an existing one. The new one probably isn't a valid enum value. It's the same problem that we get when we allow bit manipulation on enum types (which should also be disallowed IMHO). So, I think that that's more of a problem with enums than isSomeString.

Okay. I responded to that too quickly and mixed myself up a bit. The compiler already behaves this way. It unfortunately does type

return arg ~ arg;

as the enum type, but it correctly marks

return arg ~ arg[0];

as being a string, which does result in breakage. But the alternative right now is that using isSomeString breaks _all_ cases where an enum is passed rather than just some.

So, no, having isSomeString work with enums isn't perfect, but I still think that it's better than the current situation.

- Jonathan M Davis
August 05, 2012
On Saturday, 4 August 2012 at 21:09:40 UTC, Jonathan M Davis wrote:
> I think that the change to std.traits was a big mistake and will probably file it as a regression

I don't know what the right answer here is, but if you do end up reverting those changes, be sure to change std.conv.to too so that bug which led to this change doesn't come back.

Probably adding is(T == enum) and !is(T==enum) to the appropriate places will do the trick.

August 05, 2012
On Sunday, August 05, 2012 05:40:40 Adam D. Ruppe wrote:
> On Saturday, 4 August 2012 at 21:09:40 UTC, Jonathan M Davis
> 
> wrote:
> > I think that the change to std.traits was a big mistake and will probably file it as a regression
> 
> I don't know what the right answer here is, but if you do end up reverting those changes, be sure to change std.conv.to too so that bug which led to this change doesn't come back.
> 
> Probably adding is(T == enum) and !is(T==enum) to the appropriate
> places will do the trick.

Oh. Definitely. And if the appropriate unit tests are there (as they should be), then changing isSomeString should actually break them until those functions are fixed to take the isSomeString changes into account. But I'd much rather have the rare function which cares about differentiating between enums and their base types (e.g. std.conv.to) checks for that itself that make every function which doesn't care do the checking.

- Jonathan M Davis
August 05, 2012
Jonathan M Davis , dans le message (digitalmars.D:174267), a écrit :
> On Saturday, August 04, 2012 15:22:34 Jonathan M Davis wrote:
>> On Sunday, August 05, 2012 00:15:02 Timon Gehr wrote:
>> > T fun(T)(T arg) if(isSomeString!arg){
>> > 
>> >      return arg~arg[0];
>> > 
>> > }

IMO, the behavior should be this: when trying to call the template with a argument that is an enum type based on string, the compiler should try to instanciate the template for this enum type, and isSomeString should fail. Then, the the compiler will try to instanciate the template for strings, which works. Thus, the template is called with a string argument, which is the enum converted to a string.

August 05, 2012
On Sunday, August 05, 2012 12:38:56 Christophe Travert wrote:
> Jonathan M Davis , dans le message (digitalmars.D:174267), a écrit :
> > On Saturday, August 04, 2012 15:22:34 Jonathan M Davis wrote:
> >> On Sunday, August 05, 2012 00:15:02 Timon Gehr wrote:
> >> > T fun(T)(T arg) if(isSomeString!arg){
> >> > 
> >> >      return arg~arg[0];
> >> > 
> >> > }
> 
> IMO, the behavior should be this: when trying to call the template with a argument that is an enum type based on string, the compiler should try to instanciate the template for this enum type, and isSomeString should fail. Then, the the compiler will try to instanciate the template for strings, which works. Thus, the template is called with a string argument, which is the enum converted to a string.

I don't believe that the compiler ever tries twice to instantiate _any_ template. It has a specific type that it uses to instantiate the template (usually the exact type passed or the exact type of the value passed in the case of IFTI - but in the case of IFTI and arrays, it's the tail const version of the type that's used rather than the exact type). If it works, then the template is instantiated. If it fails, then it doesn't. There are no second tries with variations on the type which it could be implicitly converted to.

And honestly, I think that doing that would just make it harder to figure out what's going on when things go wrong with template instantiations. It would be like how C++ will do up to 3 implicit conversions when a function is called, so you don't necessarily know what type is actually being passed to the function ultimately. It can be useful at times, but it can also be very annoying. D explicitly did not adopt that sort of behavior, and trying multiple types when instantiating a template would not be in line with that decision.

- Jonathan M Davis
August 06, 2012
Jonathan M Davis , dans le message (digitalmars.D:174310), a écrit :
>> IMO, the behavior should be this: when trying to call the template with a argument that is an enum type based on string, the compiler should try to instanciate the template for this enum type, and isSomeString should fail. Then, the the compiler will try to instanciate the template for strings, which works. Thus, the template is called with a string argument, which is the enum converted to a string.
> 
> I don't believe that the compiler ever tries twice to instantiate _any_ template. It has a specific type that it uses to instantiate the template (usually the exact type passed or the exact type of the value passed in the case of IFTI - but in the case of IFTI and arrays, it's the tail const version of the type that's used rather than the exact type). If it works, then the template is instantiated. If it fails, then it doesn't. There are no second tries with variations on the type which it could be implicitly converted to.
> 
> And honestly, I think that doing that would just make it harder to figure out what's going on when things go wrong with template instantiations. It would be like how C++ will do up to 3 implicit conversions when a function is called, so you don't necessarily know what type is actually being passed to the function ultimately. It can be useful at times, but it can also be very annoying. D explicitly did not adopt that sort of behavior, and trying multiple types when instantiating a template would not be in line with that decision.

If someone implements a library function taking a string. People start to use that function with an enum based on string, which is fine, since enum implicitely cast to its base type. Now the library writer found a way to make is library more generic, and templatise its function to take any dchar range. All codes using enum instead of string breaks. Or there may be pressure on the library implementer to add load of template specializations to make the template work with enums. There is something wrong here: enum works for string function, but not ones that are template. It forces the user to check it the function he wants to use is a template before trying to use it with something that implicitely cast to the function's argument type. This is a problem that can be avoided by trying to instanciate the template with types that the argument implicitely cast to.

Of course, as you stated, mess can arise, because you don't know right away what template instanciation is going to be used. But there would be much less mess than in C++. First, D has a more conservative approach to implicit casting than C++. If an implicit casting is used, it will be one that is visible in the type's declaration, and that the type implementer wanted. The problems would be much more controled than for C++. Second, D has powerful template guards. You can make sure the argument's type given to the template is a of a kind that will work correctly for this function.

I don't thing the mess would be huge. Particularly for enums, which are more manifest constants than specific types in D.

-- 
Christophe
August 06, 2012
Le 04/08/2012 23:09, Jonathan M Davis a écrit :
> At present (though it didn't used to work this way until about 4 months ago -
> the change is in 2.060), a number of std.traits templates - including
> isSomeString - evaluate to false for enums. So,
>
> enum E : string { a = "hello" }
> static assert(isSomeString!(E.a));
>
> will fail. This is in spite of the fact that
>
> auto func(string a) {...}
>
> will accept E.a without complaint. So, if you change
>
> auto func(string a) {...}
>
> to
>
> auto func(T)(T a) if(isSomeString!T) {...}
>
[...]
>
> - Jonathan M Davis

E;A is a string. By definition. So isSomeString must be true.

However,

T foo(T)(T t) if (isSomeString!T) {
    return t ~ t[0];
}

must trigger an error when T is an enum type. if t has type E, which implicitly cast to string, t ~ t[0] must have type string and not E.

So the given sample code should trigger a compile time error about inconsistent return type.

The same goes for other enums operations.
« First   ‹ Prev
1 2