Expand variadic template template parameters for use in e.g. std::variant<T…>

This will be a hard nut to crack. I don’t even know if it’s possible.

My goal is to create a receive function that listens to multiple queues and pastes the object received via the particular queue (that responds first) to the stack in the return statement. This will be done via std::variant. The tricky bits are types: Every queue holds different types, so the variant inside multireceive() needs to be constructed from the template parameters used for the queues passed into it as varargs. Of course in the end the queue objects should only be passed as references.

Here’s my approach so far, but you can see I’m very unclear about how to write the pack expansions. I think I’m probably scratching the limits of C++ with this:

#include <array>
#include <variant>

template <typename T>
struct queue
{
    T queue_[100];
    
    T receive()
    {
        return queue_[0];
    }
};

// doesn't compile!
template <template <typename... T> class Q>
std::variant<T...> multireceive(Q<T>&... Arg)
{
    std::array<std::variant<Arg*...>, sizeof...(T)> queues = { (&Arg )... };

    do {
        for (size_t i=0; i < sizeof...(T); ++i) {
            if (queues[i]->receive()) {
                return *queues[i];
            }
        }
    } while(1) // busy wait
}

int main() {}

Clearly this doesn’t even remotely compile, its just me trying to show intent. The queue is of course also just a stub. Afaik you can’t even grab the template template parameter by name (T) which is very unfortunate. Has someone smarter than me already figure out how to solve that problem?

Note:

The way I did it until now is via dynamic dispatch over a non-templated base_struct. This solution however loses type information and my plan is to dispatch the variant via std::visit at the callsite of multireceive. This would then be a very neat solutions to dispatch directly from queue events.

>Solution :

Since all your queues use the same template (queue<...>), you don’t need a template template parameter (in which, by the way, the name of the nested parameter (T in your case) is ignored). You just need a type pack: typename ...T.

I also got rid of the variant array, and instead opted to iterate over the arguments directly, using a lambda in a fold expression. Though this makes extracting the return value harder, so I’ve put it into an optional:

#include <array>
#include <optional>
#include <variant>

template <typename T>
struct queue
{
    T queue_[100];
    
    T receive()
    {
        return queue_[0];
    }
};

template <typename ...P>
std::variant<P...> multireceive(queue<P> &... args)
{
    std::optional<std::variant<P...>> ret;

    while (true)
    {
        // For each `args...`:
        ([&]{
            // Do something with `args` (the current queue).
            if (args.receive())
            {
                ret.emplace(args.queue_[0]);
                return true; // Stop looping over queues.
            }
            return false;
        }() || ...);

        if (ret)
            break;
    }

    return *ret;
}

int main()
{
    queue<int> a;
    queue<float> b;

    std::variant<int, float> var = multireceive(a, b);
}

Leave a Reply