View mode: basic / threaded / horizontal-split · Log in · Help
August 04, 2012
enums and std.traits
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
Re: enums and std.traits
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
Re: enums and std.traits
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
Re: enums and std.traits
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
Re: enums and std.traits
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
Re: enums and std.traits
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
Re: enums and std.traits
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
Re: enums and std.traits
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
Re: enums and std.traits
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
Re: enums and std.traits
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
Top | Discussion index | About this forum | D home