ellimilial
ellimilial

Reputation: 1236

C++ default enum value usage

This discussion is about a name of default value: C#: Should the default value of an enum be None or Unknown?

However, large amount of people I spoken with recently considered default enum values harmful, unnecessary and potentially leading to a bad practice.

As an example consider the following:

enum eJobStates
{
    JOB_STATE_INITIALISING,
    JOB_STATE_PROCESSING,
    JOB_STATE_DONE
};

It would not make sense for a job to be in say, JOB_STATE_UNKNOWN, but you can imagine that any structure that might be used for monitoring of said jobs could use such value.

Are there any best practices / rules of thumb concerning creating a default value when defining an enum? Should they be avoided at all cost whenever possible?

Upvotes: 6

Views: 1563

Answers (1)

Tom Kerr
Tom Kerr

Reputation: 10730

An invalid default value is basically a variant in your design. The object isn't valid when it's created. You should avoid that when it's reasonable. By no means should you avoid it "at all costs."

Some problems require you to start in a variant state. In that case, you have to reason about that invalid value mentally. If you avoid naming it you are actively making your code less expressive. Think about it in terms of communication between you and the person that will have to maintain the code later.

Dealing with it downwind is annoying. You start in a variant state, but by the time it's relevant you hope that it is no longer variant. The strategy I prefer is allow users to ignore the variant state and just throw when I've made a mistake.

namespace FooType {
  enum EnumValue {
    INVALID = 0
    ,valid
  };
}

struct Foo {
  Foo() : val(FooType::INVALID) {}
  FooType::EnumValue get() const {
    if (val == FooType::INVALID)
      throw std::logic_error("variant Foo state");
    return val;
  }
  FooType::EnumValue val;
};

This frees your users from having to reason about your variance, which is worth fighting for.

If you can't get away with that, I usually prefer to degrade to safe and unsafe interfaces.

struct Foo {
  Foo() : val(FooType::INVALID) {}
  bool get(FooType::EnumValue& val_) const {
    if (val == FooType::INVALID)
      return false;
    val_ = val;
    return true;
  }
  FooType::EnumValue get() const {
    FooType::EnumValue val_;
    if (!get(val_))
      throw std::logic_error("variant Foo state");
    return val_;
  }
  FooType::EnumValue get_or_default(FooType::EnumValue def) const {
    FooType::EnumValue val_;
    if (!get(val_))
      return def;
    return val_;
  }
  FooType::EnumValue val;
};

These sorts of interfaces are good for things like databases, where null values may be expected.

Upvotes: 2

Related Questions