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?
source to share
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.
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;
};
source to share
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...);});
}
source to share