Thread overview
Casting from an enum type to another enum type
Jun 24, 2015
Roland Hadinger
Jun 24, 2015
Meta
Jun 24, 2015
Meta
Jun 24, 2015
Roland Hadinger
June 24, 2015
Hi!

What is the straightest way to safely cast from one enum type A to another enum type B, when B, in terms of values as well as identifiers, is a strict subset of A?

Ideally, that should be as simple as "to!B(a)". So far I've tried a couple of things, and one way I found was to first cast A to int, then cast that to B, but that is not really straightforward.

Example:

import std.stdio;
import std.conv;

enum Color { r, o, y, g, b, i, v }

enum StyleColor : int { o = Color.o, b = Color.b }

void main()
{
    auto c = Color.g;

    // Problem: the result is not guaranteed to be a StyleColor.
    // Does not throw, needs extra checks.
    auto d1 = cast(StyleColor) c;

    // Typesafe, but is not exactly straightforward.
    auto d2 = to!StyleColor(cast(int) c);

    // Error: template std.conv.toImpl cannot deduce
    // function from argument types !(StyleColor)(Color)
    //
    // Changing "enum StyleColor : Color" to "enum StyleColor : int"
    // in the definition above does not help either.
    auto d3 = to!StyleColor(c);
}

June 24, 2015
On Wednesday, 24 June 2015 at 15:29:03 UTC, Roland Hadinger wrote:
> Hi!
>
> What is the straightest way to safely cast from one enum type A to another enum type B, when B, in terms of values as well as identifiers, is a strict subset of A?
>
> Ideally, that should be as simple as "to!B(a)". So far I've tried a couple of things, and one way I found was to first cast A to int, then cast that to B, but that is not really straightforward.
>
> Example:
>
> import std.stdio;
> import std.conv;
>
> enum Color { r, o, y, g, b, i, v }
>
> enum StyleColor : int { o = Color.o, b = Color.b }
>
> void main()
> {
>     auto c = Color.g;
>
>     // Problem: the result is not guaranteed to be a StyleColor.
>     // Does not throw, needs extra checks.
>     auto d1 = cast(StyleColor) c;
>
>     // Typesafe, but is not exactly straightforward.
>     auto d2 = to!StyleColor(cast(int) c);
>
>     // Error: template std.conv.toImpl cannot deduce
>     // function from argument types !(StyleColor)(Color)
>     //
>     // Changing "enum StyleColor : Color" to "enum StyleColor : int"
>     // in the definition above does not help either.
>     auto d3 = to!StyleColor(c);
> }

std.conv.to really should be able to do this, but I guess not many people have needed to do this. You can write an "extension" to `to` which does it for you:

import std.traits;

auto to(To, From)(From f)
if (is(From == enum) && is(To == enum) && is(OriginalType!From : OriginalType!To))
{
    return cast(To)f;
}

enum Color { r, o, y, g, b, i, v }

enum StyleColor : int { o = Color.o, b = Color.b }

void main()
{
	auto c = Color.g;
	auto s = c.to!StyleColor;
}
June 24, 2015
Note that this is a very simple example. You need to check in the function that a valid StyleColor will actually be produced. Otherwise, it'll happily produce a StyleColor that's invalid.
June 24, 2015
On Wednesday, 24 June 2015 at 18:16:42 UTC, Meta wrote:
> std.conv.to really should be able to do this, but I guess not many people have needed to do this. You can write an "extension" to `to` which does it for you:
>
> import std.traits;
>
> auto to(To, From)(From f)
> if (is(From == enum) && is(To == enum) && is(OriginalType!From : OriginalType!To))
> {
>     return cast(To)f;
> }

Thanks, that works!

I'll add this to my project's 'helpers' module, with the "return" line replaced by:

    foreach (m; EnumMembers!To) {
        if (m == f)
            return m;
    }
    throw new ConvException("Value not in enum");