Saturday, 31 December 2011

Error defaults

The other day, I wrote a function which threw an exception when it failed to convert an ASCII string into an enumerator:

type from_ascii(const char* c)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    throw conversion_error_exception();
}

Arguably, the use of an exception here is slightly gratuitous. It's how C# does things, and it's not wrong, but it's not usually the best API when we come to use it. And of course there are performance concerns.

You usually see two solutions to this kind of problem, none of which are particularly appealing to me.

First is the 'sentinel value' solution:

enum type
{
    Left,
    Right,
    Up,
    Down,

    Unknown
};

type from_ascii(const char* c)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    return Unknown;
}

The Unknown state is somewhat untidy, though is fine for enums with limited scope within a program, where the author of the enum is likely to be the only one using it and he knows all the contexts in which an Unknown enumerator needs to be handled. Additionally, there is likely to be no overhead, assuming that the addition of the extra state doesn't require the enum's underlying type to change.

The other popular solution is the pass-by-reference method:

bool from_ascii(const char* c, type& result)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
        {
            result = (type)i;
            return true;
        }

    return false;
}

Oddly enough, C# also supports this method. However, it's a bad method as you have lost referential transparency and strict value semantics.

Here is another solution. Quite often, I find myself just defaulting an enum variable to a particular state if the conversion fails. So why not just make that part of the function?

type from_ascii(const char* c, type resultOnFailure)
{
    for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i)
        if (!strcmp(c, names[i]))
            return (type)i;

    return resultOnFailure;
}

This doesn't solve every problem though. Another technique coming soon.

No comments:

Post a Comment