Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass around parameter packs in C++?

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?

like image 278
eyelash Avatar asked Oct 22 '17 15:10

eyelash


3 Answers

Using perfect forwarding (live):

template<typename... Args>
Node(Args&&... args) {
    process(std::forward<Args>(args)...);
}
like image 74
Darklighter Avatar answered Oct 10 '22 04:10

Darklighter


Here is one example in C++11 (thanks to max66's comment) based on max66's great answer. The differences here:

  • no additional template argument for Node (instead, using a base class and constructor inheritance)
  • the desired arguments are obtained with a slightly different style than shown in the question
    • adding overloads for qualified member functions is simple
    • references are not a problem (as far as I can tell; see example below printing 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;
}
like image 2
Julius Avatar answered Oct 10 '22 06:10

Julius


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.

like image 6
max66 Avatar answered Oct 10 '22 04:10

max66