Adding optional argument to variadiac class template (C++14)

72 Views Asked by At

in our C++14 code base our modules have two types of extensions. Imagine group A as a run-at-start-up type, and type B as run-at-shut-down. The module is simply handed a list of all extension and executes them according to their group. The module distinguishes automatically in which group they are: group A has a runAtStartUp function and group B has a runAtShutDownfunction. This check is done via template check at compile time. (If an extension had both functions, it would be run both at start-up and at shut-down, but right now no such extension exists).

One of our extensions is currently always run at shut-down. It is a variadic class template looking like this:

template <typename... Ts>
class myExtension{
...
void runAtShutDown();
}

We got the request to make it possible to run it at start-up instead, without changing the default behaviour.

In order to be able to run it at start-up, we need to remove the runAtStartUp function and add the runAtShutDown function. I know how I can do that via a std::enable_if check if I have a condition at compile time. I can add a bool flag to the template argument list that would indicate which mode I want to use:

template <bool mode, typename... Ts>
class myExtension{

  template <bool enabled=mode>
  std::enable_if<enabled, void>
  runAtStartUp();

  template <bool enabled=mode>
  std::enable_if<!enabled, void>
  runAtShutDown();

}

However, I would have to add this flag to all instances where this class is used. This would require a lot of changes to modules that want to keep using the old default behaviour. I am looking for a way to only require changes when the new, non-default behaviour is to be used.

Inheriting from the existing class does not seem like the solution as I also need to remove the existing runAtShutDown function, not just add the runAtStartUp. If it is possible to do that I do now know how.

The best solution I came up with so far is to create a base class which has neither runAt... function and two inheriting classes that add only the required runAt... function. I have not tried this yet, so I'm not 100% that this actually works.

I am NOT a TMP expert, so it is possible that some of my assumptions are wrong.

Is there a good (better?) way to indicate which mode the extension should be used in and add/remove the required function?

2

There are 2 best solutions below

1
Jarod42 On BEST ANSWER

A class to transform any "shutDown extension" to "startUp extension" seems to do the job, something like:

template <typename Extension>
struct toStartUp
{
    void runAtStartUp() { extension.runAtShutDown(); }

    Extension extension;
};
2
Ted Lyngmo On

You could rename the implementation of myExtension into something new, like myExtensionEx, which takes a new template parameter that is used to decide when to run the different functions:

enum class when : int { NEVER, STARTUP, SHUTDOWN };

constexpr when operator|(when lhs, when rhs) {
    return static_cast<when>(static_cast<int>(lhs) | static_cast<int>(rhs));
}

template <when mode, when mask>
static constexpr bool is_on_v = static_cast<int>(mode) & static_cast<int>(mask);

template <when mode, class... Ts>
class myExtensionEx {
   public:
    template <bool enabled = is_on_v<mode, when::STARTUP>>
    std::enable_if_t<enabled, void> runAtStartUp() {
        std::cout << "StartUp\n";
    }

    template <bool enabled = is_on_v<mode, when::SHUTDOWN>>
    std::enable_if_t<enabled, void> runAtShutDown() {
        std::cout << "ShutDown\n";
    }
};

When the renaming is done, you need to provide the old users of myExtension with a class that only calls runAtShutDown() like before. It can then inherit from the new class to get the wanted behavior so you don't need to duplicate the code:

template <class... Ts>
class myExtension : public myExtensionEx<when::SHUTDOWN, Ts...> {
public:
    using myExtensionEx<when::SHUTDOWN, Ts...>::myExtensionEx;
    using myExtensionEx<when::SHUTDOWN, Ts...>::operator=;
};

Demo