c++overloadingenum-classpolicy-based-design

Dispatch on execution policy by type or enum?


In C++ I basically have two choices in policy based design patterns: I can use individual types (based on which an overload is selected) or specify an enum that contains all policies and I would dispatch over them at runtime. What is the preferred way of writing policy based design nowadays?

Demo

#include <cstdio>

/* Policy using types */

namespace type_policy
{
    struct detached_t{};
    struct deferred_t{};

    inline constexpr detached_t detached = detached_t{};
    inline constexpr deferred_t deferred = deferred_t{};
}

auto do_something(type_policy::detached_t pol)
{
    printf("detached type policy selected\n");
}

auto do_something(type_policy::deferred_t pol)
{
    printf("deferred type policy selected\n");
}

/* Policy using enums */

enum class enum_policy
{
    detached,
    deferred,
};

static auto do_something_else(const enum_policy pol)
{
    if (pol == enum_policy::deferred) {
        printf("deferred enum policy selected\n");
    } else {
        printf("detached enum policy selected\n");
    }
}

int main()
{
    do_something(type_policy::deferred);
    do_something_else(enum_policy::detached);
}

Note: When the enum dispatch is built into a static function, the compiler is able to eliminate the conditional at compile time as well. It is also less verbose from the start... should it be the preferred way?


Solution

  • I'd say it depends. Do you want to give clients (that includes yourself) the ability to add new policies of their own easily? If so, a type-based design would allow them to add new policies without having to recompile their existing code; all they'd need is:

    namespace type_policy
    {
        struct another_t {};
        inline constexpr another_t another = another_t{};
    }
    
    auto do_something(type_policy::another_t pol)
    {
        printf("another type policy selected\n");
    }
    

    An enum-based design, on the other hand, would only be extensible by the enum owner. Furthermore, if you change the enum to add a new policy, you'd force clients to recompile their code, even though they may not be interested in the new policy you've just added.

    Having said that, a drawback of the type-based design is that policies may end up being scattered among many files, as opposed to enum-based where all policies are nicely grouped together in the enum class.

    Another drawback of the type-based design is that it's slightly harder to use with runtime values (as you need to know the exact type to pass in do_something), as opposed to enum-based where a simple cast from an int to enum_policy will work just fine.