Jonathan Boccara's blog

When to Use Enums and When to Use Tag Dispatching in C++

Published May 1, 2018 - 10 Comments

Daily C++

Enums and tag dispatching are two ways to introduce several behaviours in the same interface in C++. With them, we can pass arguments that determine a facet of how we want a function to behave.

Even if enums and tag dispatching have that in common, they achieve it in a quite different way. Realizing what these differences are will give you tools to decide which one to use in any given situation.

To differentiate behaviour we could also use templates and runtime polymorphism based on inheritance, but I’m leaving these out of this discussion in order to focus on the differences between enums and tag dispatching specifically.

I think these differences boil down to three things: the structure they give to code, their moments of resolution, and how explicit their call site can be.

Separation of code

With tag dispatching, the code for each behaviour is localized in a separate function:

struct BehaveThisWay{};
struct BehaveThatWay{};

void f(int argument, BehaveThisWay);
{
    // use argument this way
}

void f(int argument, BehaveThatWay);
{
    // use argument that way
}

Whereas enums group the code for all the behaviours into the same function:

enum class BehaviourType
{
    thisWay,
    thatWay
};

void f(int argument, BehaviourType behaviourType);
{
    // check the value of behaviourType and behave accordingly
}

This can be either good or bad. If the various behaviours use really different code, as in std::advance for instance, then the separation of code brought by tag dispatching leads to a separation of concerns, which is a good thing.

However, if the implementation of the function is roughly the same for all behaviours, and changes only in local points in the function, then you’re better off grouping everything in the same function and testing the enum at the few places it is needed.

Also, if you have n tags argument that can take m values each, the number of overloads grows exponentially to m^n. This is sustainable for a little number of arguments only (but you don’t want your functions to accept too many arguments in general anyway).

Moments of resolution

Essentially, tags get dispatched at compile-time while enums values can be read at runtime.

Indeed, tag dispatching relies on function overloading. The calling site that passes a BehaveThisWay or a BehaveThatWay (or an object it receives from further up the call stack and that can be of one of those types) is compiled into binary code that calls either one function. So the behaviour of f for a particular call site is hardwired during compilation.

On the contrary, enums can be read at runtime, which allows deferring the value the enum takes at a particular call site until runtime, if necessary. This value could typically depend on a value coming into the system, provided by the user for example.

If the interface uses tag dispatching but the call site needs to wait until runtime to know which behaviour to choose, then its client is forced to jump through loops to use it:

if (myBehaviour == BehaviourType::thisWay)
{
    f(value, BehaveThisWay());
}
else if (myBehaviour == BehaviourType::thatWay)
{
    f(value, BehaveThatWay());
}

So if you know that your interface will be used with runtime info when you design it, you may want to consider enums over tag dispatching for that reason.

Explicit mention of the type

Finally, there is another difference between using enums and using tag dispatching: the enum forces you to write its type at call site:

f(value, BehaviourType::thisWay);

That is, if you’re using an enum class and not a C enum. But that’s what you want to use anyway, right?

You may find this extra BehaviourType more explicit, or needlessly verbose. I think it depends on taste, but I find it nice to write the type of an enum when it represents a question, to which the value of the enum is an answer.

For example, let’s consider this function that write to a file, shamelessly inspired from its Lisp counterpart:

enum class IfExists
{
    supersede,
    doNothing
};

void writeToFile(std::string const& fileName, std::string const& data, IfExists whatIfExists);

Then the call site would look like this:

writeToFile("myFile.txt", "contents", IfExists::supersede);

I find this looks lovely, doesn’t it? It’s because the enums answers a question: “what to do if it (the file) already exists?” Well, “supersede” it!

Note that you could achieve the same result with tag dispatching, if you need it for one of the reasons we saw, like separating the concerns in your code:

struct IfExists
{
    static struct Supersede {} supersede;
    static struct DoNothing {} doNothing;
};

void writeToFile(std::string const& fileName, std::string const& data, IfExists::Supersede)
{
    // supersede if file exists
}

void writeToFile(std::string const& fileName, std::string const& data, IfExists::DoNothing);
{
    // do nothing if file exists
}

It’s like a tag inside a tag, if you want. And the call site still looks like that:

writeToFile("myFile.txt", "contents", IfExists::supersede);

Varying behaviours

Now there is much more than tag dispatching and enums to determine which behaviour to execute. For example, there are virtual functions for choosing behaviour at runtime, or policy-based design (see Modern C++ Design to dive into this – I recommend you to do so) for compile-time polymorphism.

But for a local, simple choice between several behaviours, enums and tag dispatching are concise ways to do the job. And knowing the differences between the two will help you pick the right one with reasoned arguments.

Related articles:

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed