Search code examples
c++classtemplatesvariadic-templatestemplate-specialization

Specializing Template Classes with Parameter Packs


I'm using inheritance with a set of classes. One of the child classes takes in an std::function(ReturnTy<ParamTypes...>), along with the ParamTypes arguments. The class signature and constructor look like:

template<class ReturnTy, class... ParamTypes>
class Child : public Interface
{
public:
    Child(ReturnTy default_value, ParamTypes... args)
        : func_set_(false)
        , m_Args(std::make_tuple(std::forward<ParamTypes>(args)...))
        , m_ReturnValue(default_value)
    {}

private:
    bool func_set_;
    std::function<ReturnTy(ParamTypes...)> m_Funciton;
    std::tuple<ParamTypes...> m_Args;
    ReturnTy m_ReturnValue;
};

My issue is when I want to specialize for the case where there are no parameters. Furthermore, I also want to specialize for the case which ReturnTy=void and there are parameters. I found an answer that is close to what I'm looking for here, but it doesn't exactly cover what I'm trying to do because that question uses compile-time integers as template parameters, where I'm using types. It also concerns functions instead of classes. I feel like I'm close, but I just need some help to make my code work.

For reference, here is what I have for the other specializations (shortened):

template<class ReturnTy>
class Child<ReturnTy> : public Interface
{
public:
    Child(ReturnTy default_value)
        : // The same as first class without m_Args
    {}

private:
    // Same as first class without m_Args
};

template<class... ParamTypes>
class Child<void, ParamTypes...> : public Interface
{
public:
    Child(ParamTypes... args)
        : // Same as first class without m_ReturnValue

private:
    // Same as first class without m_ReturnValue
};



Edit
Specifically, the issue comes from something like the following line of code:

Child<void> obj1(5);

Solution

  • The problem is that your specializations are of the same level (no one is more specialized that the other) and Child<void> matches both.

    If you want that Child<void> matches the Child<ReturnTy> case (otherwise the solution is simple and elegant: in the second specialization, split the ParamTypes... list in a Par0 mandatory type and the rest of the ParamTypes...) I don't see a simple and elegant solution.

    The best I can imagine, at the moment, is add a level of indirection (add a Child_base class) adding also a template parameter to explicit the desired solution.

    Maybe can be made in a simpler way (sorry but, in this moment, I can try with a compiler) but I imagine something as follows

    template <typename RT, bool, typename ... PTs>
    class Child_base : public Interface
    {
       // general case (no empy PTs... list and no void return type)
    };
    
    template <typename ... PTs>
    class Child_base<void, true, PTs...> : public Interface
    {
       // case return type is void (also empy PTs... list)
    };
    
    template <typename RT>
    class Child_base<RT, false> : public Interface
    {
       // case return type only, but not void, and empy PTs
    };
    
    template <typename RT, typename ... PTs>
    class Child 
       : public Child_base<RT, std::is_same_v<void, RT>, PTs...> 
     {
     };
    

    This way, Child<void> inherit from Child_base<void, true> that matches the first specialization of Child_base but doesn't match the second one.

    I propose another way about Child: instead of define it as a class derived from Child_base, you can try defining it as a using alias of Child_base

    template <typename RT, typename ... PTs>
    using Child = Child_base<RT, std::is_same_v<void, RT>, PTs...>;
    

    Maybe renaming Child_base with a more appropriate name.