Community

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

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.

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

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

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.

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

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.

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

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

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.