Search code examples
c++variadic-templates

c++ variadic templates, arguments descendants of same base class


Consider the following base interface and two concrete implementations of it. I would like to test these with 1 template method, with arguments that are the types to be tested. All the sub-types (QueueA, QueueB) inherit from IQueue. How could I achieve an implementation for the template method tester_queues? Similar functionality can be achieved by passing in a std::vector of instances of the classes, and would be easier. I am just curious if something like what is described in method tester_queues is possible to achieve.

template<class T>
class IQueue
{
protected:
    T Data[10];
public:
    virtual const T pop()          = 0;
    virtual void     push(const T&) = 0;
};

template<class T>
class QueueA : public IQueue<T>
{
    const T pop() final {
        std::cout << "pop queue A" << std::endl;
        return T{};
    }

    void push(const T& aItemRef) final {
        std::cout << "PUSH queue A" << std::endl;
    }
};

template<class T>
class QueueB : public IQueue<T>
{
    const T pop() final {
        std::cout << "pop queue B" << std::endl;
        return T{};
    }

    void push(const T& aItemRef) final {
        std::cout << "PUSH queue B" << std::endl;
    }
};

template<class T>
void test_push_pop(IQueue<T>* aQueueP)
{
    aQueueP->push();
    aQueueP->pop();
};


template<class ... ARGS>
void tester_queues()
{
    // unpack ARGS
    // construct objects of each type into in a vector
    // std::vector<IQueue*> vec = { ARGS... };
    // then execute test_push_pop for each
    // std::for_each(vec.begin(), vec.end(), [](IQueue* itemP) {
    //      test_push_pop(itemP);
    // });
};


int main()
{
    tester_queues<QueueA<int>,
                  QueueB<char>>();
    return 0;
}

Solution

  • If you really want to specify only the Queue types as template arguments, then you can use something like this to let tester_queues() construct instances of those type internally for testing:

    #include <iostream>
    
    template<class T>
    class IQueue
    {
    protected:
        T Data[10];
    public:
        virtual const T pop()          = 0;
        virtual void    push(const T&) = 0;
    };
    
    template<class T>
    class QueueA : public IQueue<T>
    {
    public:
        const T pop() final {
            std::cout << "pop queue A" << std::endl;
            return T{};
        }
    
        void push(const T& aItemRef) final {
            std::cout << "PUSH queue A" << std::endl;
        }
    };
    
    template<class T>
    class QueueB : public IQueue<T>
    {
    public:
        const T pop() final {
            std::cout << "pop queue B" << std::endl;
            return T{};
        }
    
        void push(const T& aItemRef) final {
            std::cout << "PUSH queue B" << std::endl;
        }
    };
    
    template <class... Ts>
    struct Tester;
    
    template <class T, class... Ts>
    struct Tester<T, Ts...>
    {
        template<class U>
        static void test_push_pop(IQueue<U>&& aQueueP)
        {
            aQueueP.push(U{});
            aQueueP.pop();
        }
    
        static void test_push_pops()
        {
            test_push_pop(T{});
            Tester<Ts...>::test_push_pops();
        }
    };
    
    template<>
    struct Tester<>
    {
        static void test_push_pops() {}
    };
    
    template<class... Ts>
    void tester_queues()
    {
        Tester<Ts...>::test_push_pops();
    };
    
    int main()
    {
        tester_queues< QueueA<int>, QueueB<char> >();
        return 0;
    }
    

    Live Demo

    Alternatively, you can construct your own objects of the desired types and then pass them into tester_queues() as input parameters, eg:

    #include <iostream>
    #include <utility>
    
    template<class T>
    class IQueue
    {
    protected:
        T Data[10];
    public:
        virtual const T pop()          = 0;
        virtual void    push(const T&) = 0;
    };
    
    template<class T>
    class QueueA : public IQueue<T>
    {
    public:
        const T pop() final {
            std::cout << "pop queue A" << std::endl;
            return T{};
        }
    
        void push(const T& aItemRef) final {
            std::cout << "PUSH queue A" << std::endl;
        }
    };
    
    template<class T>
    class QueueB : public IQueue<T>
    {
    public:
        const T pop() final {
            std::cout << "pop queue B" << std::endl;
            return T{};
        }
    
        void push(const T& aItemRef) final {
            std::cout << "PUSH queue B" << std::endl;
        }
    };
    
    template<class T>
    void test_push_pop(IQueue<T>& aQueueP)
    {
        aQueueP.push(T{});
        aQueueP.pop();
    }
    
    void test_push_pops()
    {
    }
    
    template<class T, class... Ts>
    void test_push_pops(T&& aQueueP, Ts&&... aQueuePs)
    {
        test_push_pop(std::forward<T>(aQueueP));
        test_push_pops(aQueuePs...);
    };
    
    template<class... Ts>
    void tester_queues(Ts&&... args)
    {
        test_push_pops(args...);
    };
    
    int main()
    {
        tester_queues( QueueA<int>{}, QueueB<char>{} );
        return 0;
    }
    

    Live Demo