Search code examples
c++templatesvariadic-templatesparameter-pack

Multiple parameter packs as factory function arguments


I am trying to create a factory method that instantiates a template class by passing the constructor arguments for the component classes as template parameter packs.

The ProducerConsumer template is parameterized on the types of the Producer and Consumer, which are aggregated by ProducerConsumer. Arguments to the factory function depend on the constructor arguments of the parameterizing classes.

I've studied How can I have multiple parameter packs in a variadic template?; the highest voted answer only counts the arguments in the parameter pack, it does not use them. I've also looked Multiple parameter packs — how?, which is rather uninformative for my problem as it passes a variable number of arguments of the same type.

What I have now generates errors at the std::make_tuple about not being able to cast to tuple's private base class.

How do I forward a variable number and types of arguments to two different constructors within the factory function, ProducerConsumer::create()? Also, if and how should r-value reference be applied to the parameter packs being forwarded?

#include <iostream>
#include <tuple>
#include <chrono>


using Duration = std::chrono::steady_clock::duration;


struct ProducerBase {
    ProducerBase(Duration maxWait) 
        : maxWait_{ maxWait }
    {}
    Duration maxWait_;
};

struct ConsumerBase {
    ConsumerBase(int capacity, Duration maxWait) :
        capacity_{ capacity },
        maxWait_{ maxWait }
    {}
    int capacity_;
    Duration maxWait_;
};


template <typename Producer, typename Consumer>
struct ProducerConsumer {
    
    template < typename ... ProducerArgs, typename ... ConsumerArgs >
        static ProducerConsumer* create(
            std::tuple<ProducerArgs&& ...> producerArgs,
            std::tuple<ConsumerArgs&& ...> consumerArgs
        )
    {
        auto producer = new Producer(std::forward<ProducerArgs>(producerArgs)...);
        auto consumer = new Consumer(std::forward<ConsumerArgs>(consumerArgs)...);
        return new ProducerConsumer<Producer, Consumer>(producer, consumer);
    }

    ProducerConsumer(ProducerBase* producer, ConsumerBase* consumer)
        :
        producer_{ producer },
        consumer_{ consumer }
    {}

    ProducerBase* producer_;
    ConsumerBase* consumer_;
};

using namespace std::chrono_literals;

int main(int argc, char* argv[]) {
    Duration const MaxWait{ 10s };
    int const Capacity{ 32 };
    using PC = ProducerConsumer<ProducerBase, ConsumerBase>;
    auto pc = PC::create(std::make_tuple(MaxWait), std::make_tuple(Capacity, MaxWait));
    return 0;
}

Solution

  • Two issues:

    • forwarding reference is reserved to T&&, C<T&&> is not forwarding reference.

    • tuple variable is not a variadic variable, you cannot use ... on it. in C++17, there is std::make_from_tuple to do what you want:

    template < typename ... ProducerArgs, typename ... ConsumerArgs >
        static ProducerConsumer* create(
            std::tuple<ProducerArgs...> producerArgs,
            std::tuple<ConsumerArgs...> consumerArgs
        )
    {
        auto producer = new Producer(std::make_from_tuple<Producer>(producerArgs));
        auto consumer = new Consumer(std::make_from_tuple<Consumer>(consumerArgs));
        return new ProducerConsumer<Producer, Consumer>(producer, consumer);
    }
    

    Demo

    And as you don't use variadic, you might simplify it to

        template < typename ProducerTupleArgs, typename ConsumerTupleArgs >
            static ProducerConsumer* create(
                ProducerTupleArgs&& producerArgs,
                ConsumerTupleArgs&& consumerArgs
            )
        {
            auto producer = new Producer(std::make_from_tuple<Producer>(std::forward<ProducerTupleArgs>(producerArgs)));
            auto consumer = new Consumer(std::make_from_tuple<Consumer>(std::forward<ConsumerTupleArgs>(consumerArgs)));
            return new ProducerConsumer<Producer, Consumer>(producer, consumer);
        }
    
    

    Demo