1

I have a templated base class which defines some sort of default functionality which a templated derived class can overload if it wants to. This "default" functionality is implemented as variadic template functions taking any argument.

If a user fails to properly define the derived class' prototype then the base class method will unintentionally be called.

Conversely, if the writer of the derived class wishes to implement a new prototype for an existing function then the Base class will become obsolete for that function name (it won't provide generic "fallback" for other derived methods that choose not to define this particular prototype of the hook).

So... rather than ramble on I'll give an example of what I'm trying to do:

template <typename Derived>
class Base
{
    template <typename... Args>
    void hook1(const Args&...) // -> std::enable_if(Derived::hook1 doesn't exist)
    {
        // do nothing
    }

    template <typename... Args>
    void hook2(const Args&...) // -> std::enable_if(Derived::hook2 doesn't exist)
    {
        // do nothing
    }

    // ... hook3, hook4, etc.
};

// this particular derived class overloads only hook1
class DerivedExample : public Base<DerivedExample>
{
    template <typename SomeArg>
    void hook1(const SomeArg& arg)
    {
        // do something
    }
};

Clearly, those enable_if statements don't make any sense, but they illustrate the functionality I would like to have. Is this achievable?

quant
  • 21,507
  • 32
  • 115
  • 211

1 Answers1

2

First you're going to have to define some traits. In particular one that tests the presence of a member function like so:

struct has_hook_one_impl {
    template<typename T>
    static auto test(int) -> decltype(std::declval<T&>().hook1(0), std::true_type{});
    template<typename...>
    static std::false_type test(...);
};

template<typename T>
struct has_hook_one : public decltype(has_hook_one_impl::test<T>(0)) {};

This trait uses expression SFINAE to detect the presence of the member, you can read more about expression SFINAE here.

After this trait is defined its usage becomes simple and you could define others that are similar to it. So in your case, the enable_if would be like this:

template <typename Derived>
class Base
{
    template <typename... Args>
    auto hook1(const Args&...) -> typename std::enable_if<!has_hook_one<Derived>::value>::type
    {
        // do nothing
    }
};

Implementing the other meta function that detects the presence of the other member functions should be easy enough to extend and was left out as an exercise to the reader.

Community
  • 1
  • 1
Rapptz
  • 20,807
  • 5
  • 72
  • 86
  • Cool. In my case, I have a lot of hooks; would it be possible to add hooks / change them without needing to modify the "has_hook" check? For example, could `has_hook` take a function type as a parameter? How would this look? – quant Jul 31 '14 at 23:20
  • 1
    @Arman Unfortunately not. C++ lacks compile-time reflection, it'd be really helpful if it had it though. You're going to have to implement traits for each one. You can write a macro that would make these traits "automatically" for you. – Rapptz Jul 31 '14 at 23:23
  • I'm having some trouble getting this to work. Here's my attempt at implementing what you suggested: http://ideone.com/3HSy6Z – quant Aug 01 '14 at 00:21
  • 1
    @Arman The error is pretty clear (you missed `auto`). See here: http://ideone.com/TJRBqy – Rapptz Aug 01 '14 at 00:22