Extracting package types from a variadic template class and declaring an argument of the same package type

First of all, sorry for the confusing title of the question, feel free to edit if you're thinking of a better way to state this.

I have a class:

template <typename ...Arguments>
class CSignal
{
    template <typename ...ActualArguments>
    void invoke(ActualArguments&&... args) const {}
};

      

And one more, this is what I'm having a problem with:

class SomeClass
{
    template<typename ...Arguments>
    void invokeQueued(CSignal<Arguments...>& signal, const Arguments&... args)
    {
        m_queue.emplace_back([=](){signal.invoke(args...);});
    }

    std::deque<std::function<void (void)>> m_queue;
};

      

Problem:

CSignal<float> signal;
int i = 0;
SomeClass().invokeQueued(signal, i);

      

Mistake:

template parameter 'Arguments' is ambiguous
could be 'float'
or       'int'

      

Possible naive solution

template<typename ...FormalArguments, typename ...ActualArguments>
void invokeQueued(CSignal<FormalArguments...>& signal, const ActualArguments&... args)
{
    m_queue.emplace_back([=](){signal.invoke(args...);});
}

      

unacceptable in this particular case, because I need to capture the arguments by value (copy them into a lambda), and the conversion from ActualArguments

to FormalArguments

must happen at the time of the call invokeQueued

, not when the lambda is called.

If I could typedef

wrap the arguments in a class CSignal

, I would do:

template<typename ...FormalArguments>
void invokeQueued(CSignal<FormalArguments...>& signal, const CSignal<FormalArguments...>::argument_types&... args)
{
    m_queue.emplace_back([=](){signal.invoke(args...);});
}

      

But this is not possible. Solutions?

+3


source to share


2 answers


The error you encounter arises from the fact that the types of the arguments are different, while for each such pair the compiler has only one template pattern: CSignal

it sees from the signature float

, and from the deduced type of the second argument it sees int

, and both should be mapped to one item in the package Arguments

. This is where ambiguity arises.

To work around this, you can exclude one of the parameters from the template argument output by introducing non-inferred context, for example with the trick below:

template <typename T> struct identity { using type = T; };
template <typename T> using identity_t = typename identity<T>::type;

class SomeClass
{
public:
    template <typename... Arguments>
    void invokeQueued(CSignal<Arguments...>& signal,
                      const identity_t<Arguments>&... args)
    //                      ~~~~~~~~~^
    {
        m_queue.emplace_back([=](){signal.invoke(args...);});
    }

    std::deque<std::function<void(void)>> m_queue;
};

      

The compiler won't try to infer any template parameter that is part of the nested name specifier syntax, and that's basically what it does identity

- it enters the syntax identity<T>::type

so it T

stays in scope but can still be used in a function declaration.

DEMO




Alternatively, you can store the decayed copies of the arguments converted to the appropriate types at the time of capture in a lambda expression (C ++ 14):

#include <utility>
#include <type_traits>
#include <cstddef>

class SomeClass
{
public:
    template <typename... FormalArguments, typename... ActualArguments>
    void invokeQueued(CSignal<FormalArguments...>& signal, ActualArguments&&... args)
    {
        invokeQueued(signal, std::index_sequence_for<ActualArguments...>{}, std::forward<ActualArguments>(args)...);
    }

    template <typename... FormalArguments, typename... ActualArguments, std::size_t... Is>
    void invokeQueued(CSignal<FormalArguments...>& signal, std::index_sequence<Is...>, ActualArguments&&... args)
    {
        m_queue.emplace_back(
          [signal, t = std::tuple<std::decay_t<FormalArguments>...>(std::forward<ActualArguments>(args)...)]
          (){signal.invoke(std::get<Is>(t)...);});
    }

    std::deque<std::function<void(void)>> m_queue;
};

      

DEMO 2

+8


source


Use an identification trick to create a non-displayable context for Arguments&&...

.



template <typename T>
class Identity
{
public:
using type = T;
};

template<typename ...Arguments>
void invokeQueued(CSignal<Arguments...>& signal, const typename Identity<Arguments>::type &... args)
{
    m_queue.emplace_back([=](){signal.invoke(args...);});
}

      

+1


source







All Articles