Search code examples
c++templatesvariadic-templatestype-alias

Template alias of a more generic template using externally defined template type as a template parameter


I wanted to take a well-defined template class which depends on boost::signals2 and abstract it one generic layer deeper which could remove the dependency, permitting a different implementation to be used but still allowing the higher-level functions to be used by depending on the more generic interface instead of the more specific one. In my refactor, I'm completely stuck. And, I already know I'm a little out of my league here. I just started really learning C++ over the course of this year. But, it's a little fun. But there's something I'm missing from my understanding of templates. To start off, the library I'm working on will be using C++17, I'm using clang++ (Mac), and depending on Boost ~1.7.2 Signals2.

Consider the following excerpts:

#include <boost/signals2.hpp>

template<typename Functional_T, typename Return_T, typename ...Args_T>
using SignalHandler = typename Functional_T::template Functional_T<Return_T(Args_T...)>;

template<typename Connection_T>
struct SignalConnection; //no issues here, just wraps a private Connection_T with a getter/setter

template<typename Connection_T, typename Functional_T, typename Return_T, typename ...Args_T>
struct ISignalEmitter
{
protected:
    virtual Return_T trigger(Args_T ...args) = 0;
public:
    virtual void disconnectAll() = 0;
    virtual SignalConnection<Connection_T> onSignal(SignalHandler<Functional_T, Return_T, Args_T...> &signalHandler) = 0;
    virtual void cancelOnSignal(const SignalConnection<Connection_T> &connection) = 0;
};

But here's where I'm completely stuck. The thing I want to define, in my mind looks like this:

template<typename Return_T, typename ...Args_T>
using BoostSignals2SignalHandler = SignalHandler<boost::signals2::slot, Return_T, Args_T...>;

And I think we all know what happens. slot requires template arguments. So how can I construct this so that it works? I've tried a variety of things, but I'm stuck. My next guess is:

template<typename Return_T, typename ...Args_T>
using BoostSignals2SignalHandler = SignalHandler<template boost::signals2::slot::template, Return_T, Args_T...>;

I hate to ask, but I know I can't be the first person trying to solve this riddle. And there must be a way to think of these templates in a way that's comprehensible and not just guessing and testing. I've got a template alias BoostSignals2SignalHandler that is a specific version of SignalHandler minus the first template argument plus using an existing template as the specific template argument for the more generic template.

I would ask, "is this poor design?," but I feel like the concept is adequate to solve the problem I want to solve. I just don't understand what to do exactly. Can anyone point me in the right direction? Again, I hate to ask. This one's been a headache figuring out. Thanks in advance!


Solution

  • Instead of listing your template arguments as Return_T, Args_T... I would follow Signals2 example and use a single parameter of function type like Return_T(Args_T...) in SignalHandler.

    Then we simply change Functional_T to a template template parameter.

    template<template <typename> typename Functional_T, typename Signature_T>
    using SignalHandler = Functional_T<Signature_T>;
    

    Now the first parameter passed to SignalHandler must be a template that can be instatiated with 1 template parameter. Since you are using c++17 we can pass templates with more parameter as well, as long as they have default values.

    To get Return_T and Args_T... in ISignalEmitter we use partial specialization.

    template<typename Connection_T, template <typename> typename Functional_T, typename Signature_T>
    struct ISignalEmitter; // Base declaration to match the specialization against
    
    template<typename Connection_T, template <typename> typename Functional_T, typename Return_T, typename ...Args_T>
    struct ISignalEmitter<Connection_T, Functional_T, Return_T(Args_T...)> // Matching Return_T and Args_T against Signature_T
    {
    protected:
        virtual Return_T trigger(Args_T ...args) = 0;
    public:
        virtual void disconnectAll() = 0;
        virtual SignalConnection<Connection_T> onSignal(SignalHandler<Functional_T, Return_T(Args_T...)> &signalHandler) = 0;
        virtual void cancelOnSignal(const SignalConnection<Connection_T> &connection) = 0;
    };
    

    At this point you can define BoostSignals2SignalHandler like so

    template<typename Signature_T>
    using BoostSignals2SignalHandler = SignalHandler<boost::signals2::slot, Signature_T>;
    
    using MySignalHandler = BoostSignals2SignalHandler<void()>;