Consider the following example:
template <class T> class method_traits;
template <class T, class Ret, class... Arg> class method_traits<Ret(T::*)(Arg...)> {
public:
using type = Arg; // this does not work
};
template <class T> using argument_types = typename method_traits<T>::type;
template <class T> class Node {
T t;
public:
Node(Input<argument_types<decltype(&T::process)>>... inputs) { // how do I make this work?
...
}
};
The arguments of the constructor of Node<T>
depend on the arguments of the method T::process
. So if a type T
has a method process
of the signature float process(float a, int b)
the signature of the constructor of Node<T>
should look like this: Node(Input<float> a, Input<int> b)
.
How do I extract the parameter pack from T::process
to use it on the constructor of Node
?
Using perfect forwarding (live):
template<typename... Args>
Node(Args&&... args) {
process(std::forward<Args>(args)...);
}
Here is one example in C++11 (thanks to max66's comment) based on max66's great answer. The differences here:
Node
(instead, using a base class and constructor inheritance)42
)http://coliru.stacked-crooked.com/a/53c23e1e9774490c
#include <iostream>
template<class... Ts> struct Types {};
template<class R, class C, class... Args>
constexpr Types<Args...> get_argtypes_of(R (C::*)(Args...)) {
return Types<Args...>{};
}
template<class R, class C, class... Args>
constexpr Types<Args...> get_argtypes_of(R (C::*)(Args...) const) {
return Types<Args...>{};
}
template<class T, class ConstructorArgs>
struct NodeImpl;
template<class T, class Arg0, class... Args>
struct NodeImpl<T, Types<Arg0, Args...>> {
NodeImpl(Arg0 v0, Args...) {
v0 = typename std::decay<Arg0>::type(42);
(void)v0;
}
};
template<class T>
struct Node
: NodeImpl<T, decltype(get_argtypes_of(&T::process))>
{
using ConstructorArgs = decltype(get_argtypes_of(&T::process));
using NodeImpl<T, ConstructorArgs>::NodeImpl;
};
struct Foo {
void process(int, char, unsigned) const {}
};
struct Bar {
void process(double&) {}
};
int main() {
Node<Foo> foo_node{4, 'c', 8u};
double reftest = 2.0;
Node<Bar> bar_node{reftest};
std::cout << reftest << std::endl;
}
Obviously you can't save a list of types in this way
using type = Arg;
where Arg
is a variadic list of types.
But you can save they in a type container and std::tuple
can do this works too. So I suggest to modify the method_traits
specialization as follows
template <typename T>
struct method_traits;
template <typename T, typename Ret, typename... Args>
struct method_traits<Ret(T::*)(Args...)>
{ using tTypes = std::tuple<Args...>; };
and rewrite argument_types
to intercept the std::tuple
template <typename T>
using tTypes = typename method_traits<T>::tTypes;
Now you can use the default template value and partial specialization trick defining node
template <typename T, typename TArgs = tTypes<decltype(&T::process)>>
struct Node;
In this way, instantiating a Node<T>
object, you effectively get a Node<T, tTypes<decltype(&T::process)>
that is a Node<T, std::tuple<Args...>>
with the wanted Args...
.
So you can simply define the following partial specialization of Node
as follows
template <typename T, typename ... Args>
struct Node<T, std::tuple<Args...>>
{
T t;
Node (Input<Args> ... inputs)
{ /* do something */ }
};
The following is a full working example
#include <tuple>
#include <type_traits>
template <typename T>
struct tWrapper
{ using type = T; };
template <typename T>
using Input = typename tWrapper<T>::type;
template <typename T>
struct method_traits;
template <typename T, typename Ret, typename... Args>
struct method_traits<Ret(T::*)(Args...)>
{ using tTypes = std::tuple<Args...>; };
template <typename T>
using tTypes = typename method_traits<T>::tTypes;
template <typename T, typename TArgs = tTypes<decltype(&T::process)>>
struct Node;
template <typename T, typename ... Args>
struct Node<T, std::tuple<Args...>>
{
T t;
Node (Input<Args> ... inputs)
{ /* do something */ }
};
struct foo
{
float process (float a, int b)
{ return a+b; }
};
int main ()
{
Node<foo> nf(1.0f, 2);
}
-- EDIT --
As pointed by Julius (and the OP themselves) this solution require an additional template type with a default template value.
In this simplified case isn't a problem but I can imagine circumstances where this additional template argument can't be added (by example: if Node
receive a variadic list of template arguments).
In those cases, Julius propose a way that complicate a little the solution but permit to avoid the additional template parameter for Node
: to add a template base class, that receive the TArgs
arguments, and to works with constructor inheritance.
That is: defining a NodeBase
as follows
template <typename, typename>
struct NodeBase;
template <typename T, typename ... Args>
struct NodeBase<T, std::tuple<Args...>>
{
T t;
NodeBase (Input<Args> ...)
{ /* do something */ }
};
there is no need for an additional template parameter, for Node
, that can simply written as
template <typename T>
struct Node
: public NodeBase<T, tTypes<decltype(&T::process)>>
{ using NodeBase<T, tTypes<decltype(&T::process)>>::NodeBase; };
Julius, following this idea, prepared a solution that (IMHO) is even better and interesting.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With