Thread overview
Nothrow std.conv.to with explicit default value
Jun 18, 2018
Per Nordlöw
Jun 18, 2018
Adam D. Ruppe
Jun 20, 2018
Per Nordlöw
Jun 20, 2018
Per Nordlöw
Jun 20, 2018
Per Nordlöw
Jun 20, 2018
Per Nordlöw
Jun 20, 2018
Per Nordlöw
Jun 21, 2018
Per Nordlöw
Jun 20, 2018
Jonathan M Davis
June 18, 2018
I have a nothrow variant of std.conv.to defined as follows:

T toDefaulted(T, S, U)(S value, /*lazy*/ U defaultValue)
if (is(typeof(() { T r = defaultValue; }))) // TODO use std.traits.isAssignable!(T, U) ?
{
    try
    {
        import std.conv : to;
        return value.to!T;
    }
    catch (Exception e) // assume `ConvException`. TODO can we capture `ConvException` instead make it inferred `nothrow`
    {
        return defaultValue;
    }
}

The problem with this code is that throwing exceptions for the default case is costly at least with dmd. Is there another way to do this?
June 18, 2018
On Monday, 18 June 2018 at 20:48:55 UTC, Per Nordlöw wrote:
> T toDefaulted(T, S, U)(S value, /*lazy*/ U defaultValue)
> if (is(typeof(() { T r = defaultValue; }))) // TODO use std.traits.isAssignable!(T, U) ?

why not just make it

T toDefaulted(T, S)(S value, T defaultValue)

and forget U entirely?

> The problem with this code is that throwing exceptions for the default case is costly at least with dmd. Is there another way to do this?

It depends on what the types are. If it is like string to int, you can simply scan the string for the appropriate format.

For other conversions though, I don't think you can tell in general. User-defined types might not even tell you except by exception.
June 18, 2018
On 6/18/18 4:48 PM, Per Nordlöw wrote:
> The problem with this code is that throwing exceptions for the default case is costly at least with dmd. Is there another way to do this?

Yes, have an internal implementation which doesn't throw, but rather returns an error code. Then you can call that and throw or use default value based on the return value.

It just means re-doing std.conv.to, which is pretty hairy, but also pretty well-organized.

-Steve
June 20, 2018
On Monday, 18 June 2018 at 21:10:03 UTC, Steven Schveighoffer wrote:
> It just means re-doing std.conv.to, which is pretty hairy, but also pretty well-organized.

Ok. Where in std.conv do the string-to-enum conversions take place?
June 20, 2018
On Wednesday, 20 June 2018 at 09:27:14 UTC, Per Nordlöw wrote:
> On Monday, 18 June 2018 at 21:10:03 UTC, Steven Schveighoffer wrote:
>> It just means re-doing std.conv.to, which is pretty hairy, but also pretty well-organized.
>
> Ok. Where in std.conv do the string-to-enum conversions take place?

AFAICT, string-to-enum-conversion must include a switch containing a static foreach over all the enumerators, right?
June 20, 2018
On Wednesday, 20 June 2018 at 09:37:00 UTC, Per Nordlöw wrote:
> AFAICT, string-to-enum-conversion must include a switch containing a static foreach over all the enumerators, right?

My suggestion for nothrow @nogc string-to-enum conversion with default value


T toDefaulted(T)(scope const(char)[] value,
                 T defaultValue) @safe pure nothrow @nogc
if (is(T == enum))
{
    switch (value)
    {
        static foreach (index, member; __traits(allMembers, T))
        {
        case member:
            return cast(T)(index);
        }
    default:
        return defaultValue;
    }
}

@safe pure nothrow /*TODO @nogc*/ unittest
{
    enum E { unknown, x, y, z }
    assert("x".toDefaulted!(E)(E.init) == E.x);
    assert("_".toDefaulted!(E)(E.init) == E.unknown);
}
June 20, 2018
On Wednesday, 20 June 2018 at 09:52:04 UTC, Per Nordlöw wrote:
> My suggestion for nothrow @nogc string-to-enum conversion with default value
>
>
> T toDefaulted(T)(scope const(char)[] value,
>                  T defaultValue) @safe pure nothrow @nogc
> if (is(T == enum))
> {
>     switch (value)
>     {
>         static foreach (index, member; __traits(allMembers, T))
>         {
>         case member:
>             return cast(T)(index);
>         }

Oops, this doesn't work for enums with "holes". How do I most easily fix that?
June 20, 2018
On Wednesday, June 20, 2018 09:37:00 Per Nordlöw via Digitalmars-d-learn wrote:
> On Wednesday, 20 June 2018 at 09:27:14 UTC, Per Nordlöw wrote:
> > On Monday, 18 June 2018 at 21:10:03 UTC, Steven Schveighoffer
> >
> > wrote:
> >> It just means re-doing std.conv.to, which is pretty hairy, but also pretty well-organized.
> >
> > Ok. Where in std.conv do the string-to-enum conversions take place?
>
> AFAICT, string-to-enum-conversion must include a switch containing a static foreach over all the enumerators, right?

If you want to know where std.conv deals with converting enums, look for EnumMembers, since that's the trait to get all of the members of an enum.

- Jonathan M Davis


June 20, 2018
On Wednesday, 20 June 2018 at 09:54:29 UTC, Per Nordlöw wrote:
>> T toDefaulted(T)(scope const(char)[] value,
>>                  T defaultValue) @safe pure nothrow @nogc
>> if (is(T == enum))
>> {
>>     switch (value)
>>     {
>>         static foreach (index, member; __traits(allMembers, T))
>>         {
>>         case member:
>>             return cast(T)(index);
>>         }
>
> Oops, this doesn't work for enums with "holes". How do I most easily fix that?

This is my solution:

T toDefaulted(T)(scope const(char)[] value, T defaultValue) @safe pure nothrow @nogc
if (is(T == enum))
{
    switch (value)
    {
        static foreach (member; __traits(allMembers, T))
        {
        case member:
            mixin(`return T.` ~ member ~ `;`);
        }
    default:
        return defaultValue;
    }
}

Is there a way to avoid compile-time-string-concat plus mixin here?
June 21, 2018
On Wednesday, 20 June 2018 at 14:39:48 UTC, Per Nordlöw wrote:
> Is there a way to avoid compile-time-string-concat plus mixin here?

Using __traits(getMember, ...) should compile faster, right?

T toDefaulted(T)(scope const(char)[] value, T defaultValue) @safe pure nothrow @nogc
if (is(T == enum))
{
    // doesn't need `std.conv.to`
    switch (value)
    {
        static foreach (member; __traits(allMembers, T)) // prevents call to slower `EnumMembers`
        {
        case member:
            return __traits(getMember, T, member); // NOTE this is slower: mixin(`return T.` ~ member ~ `;`);
        }
    default:
        return defaultValue;
    }
}