如何在C++中传递参数包?

10
考虑以下示例:
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?
        ...
    }
};
Node<T> 的构造函数参数取决于 T::process 方法的参数。因此,如果类型 T 有一个签名为 float process(float a, int b) 的方法 process,那么 Node<T> 的构造函数签名应该如下所示:Node(Input<float> a, Input<int> b)
如何从 T::process 中提取参数包以在 Node 的构造函数中使用?

2
你需要将Node也变成可变参数模板。 - n. m.
{btsdaf} - eyelash
1
@n.m. 肯定只需要将Node的构造函数变成可变参数模板,而不是整个类? - Arthur Tacca
@ArthurTacca 这取决于 Node 存储了什么。 - n. m.
2
{btsdaf} - aschepler
显示剩余4条评论
5个回答

6
显然,你不能以这种方式保存类型列表。
    using type = Arg;

其中Arg是一种可变类型列表。

但你可以将它们保存在类型容器中,std::tuple也可以完成这项工作。因此,我建议将method_traits的特化修改为以下内容:

template <typename T>
struct method_traits;

template <typename T, typename Ret, typename... Args>
struct method_traits<Ret(T::*)(Args...)>
 { using tTypes = std::tuple<Args...>; };

重写 argument_types 以拦截 std::tuple

template <typename T>
using tTypes = typename method_traits<T>::tTypes;

现在,您可以使用默认模板值和部分特化技巧来定义节点。
template <typename T, typename TArgs = tTypes<decltype(&T::process)>>
struct Node;

以这种方式实例化Node<T>对象,你可以有效地获得一个Node<T, tTypes<decltype(&T::process)>,它是一个带有所需Args...Node<T, std::tuple<Args...>>
因此,你可以简单地定义以下Node的部分特化,如下所示。
template <typename T, typename ... Args>
struct Node<T, std::tuple<Args...>>
 {
   T t;

   Node (Input<Args> ... inputs)
    { /* do something */ }
 };

下面是一个完整的工作示例。
#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);
 }

--编辑--

正如Julius(和原帖作者)所指出的,此解决方案需要具有默认模板值的额外模板类型。

在这种简化情况下并不是问题,但我可以想象到某些情况下无法添加此额外模板参数(例如:如果Node接收可变数量的模板参数)。

在这些情况下,Julius提出了一种稍微复杂但可以避免为Node添加额外模板参数的方法:添加一个模板基类,该基类接收TArgs参数,并使用构造函数继承。

也就是说:定义一个NodeBase如下:

template <typename, typename>
struct NodeBase;

template <typename T, typename ... Args>
struct NodeBase<T, std::tuple<Args...>>
 {
   T t;

   NodeBase (Input<Args> ...)
    { /* do something */ }
 };

不需要为Node添加额外的模板参数,可以简单地写成:

template <typename T>
struct Node
   : public NodeBase<T, tTypes<decltype(&T::process)>>
 { using NodeBase<T, tTypes<decltype(&T::process)>>::NodeBase; };

根据这个想法,Julius 准备了一种解决方案,(在我看来)这个方案甚至更好且有趣。


不错的解决方案,尽管它需要向Node添加第二个模板参数。 - eyelash
@eyelash - 是的;但第二个参数是默认值;因此,正如您在main()中所看到的那样,您可以简单地实例化一个Node<foo> - max66
非常好的解决方案。如果默认模板参数不可接受,可以从基类派生并进行构造函数继承。 - Julius
{btsdaf} - max66

3

使用完美转发 (现场演示):

template<typename... Args>
Node(Args&&... args) {
    process(std::forward<Args>(args)...);
}

1
老实说,这就是答案,以上的所有答案都过于复杂了。 - sleepystar96

2
这是一个基于max66的优秀答案的C++11示例(感谢max66的评论)。以下是不同之处:
  • 没有额外的模板参数用于Node(而是使用基类和构造函数继承)
  • 所需的参数是以与问题中显示略有不同的方式获得的。
    • 为限定成员函数添加重载很简单
    • 引用不是问题(据我所知;请参见下面打印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;
}

1
{btsdaf} - max66
1
{btsdaf} - max66

0

使用私有继承和CRTP怎么样?

#include <tuple>
#include <iostream>

template <typename Method> struct method_traits;

template <typename T, typename Ret, typename... Args>
struct method_traits<Ret(T::*)(Args...)> {
public:
    using parameter_pack = std::tuple<Args...>;
};

template <typename Derived, typename Tuple> struct Base;

template <typename Derived, typename... Ts>
struct Base<Derived, std::tuple<Ts...>> {
    void execute_constructor(Ts&&... ts) { 
        Derived* d = static_cast<Derived*>(this);
        d->t.process(std::forward<Ts>(ts)...);
        d->num = sizeof...(Ts);
    }
    virtual ~Base() = default;
};

template <typename T, typename... Rest>
class Node : Base<Node<T, Rest...>, typename method_traits<decltype(&T::process)>::parameter_pack> {
    T t;
    int num;
public:
    using Base = Base<Node<T, Rest...>, typename method_traits<decltype(&T::process)>::parameter_pack>;
    friend Base;  // So that Base can do whatever it needs to Node<T, Rest...>'s data members.
    template <typename... Ts>
    Node (Ts&&... ts) {
        Base::execute_constructor(std::forward<Ts>(ts)...);
        std::cout << "num = " << num << '\n';
    }
};

struct foo {
    void process(int a, char c, bool b) {
        std::cout << "foo(" << a << ", " << c << ", " << std::boolalpha << b << ") carried out.\n";
    }   
};

int main() {
    Node<foo> n(5, 'a', true);
    std::cin.get();
}

输出:

foo(5, a, true) carried out.
num = 3

0

这里是一个使用C++14的解决方案。(注意:我只在clang中测试过它):

#include <string>
#include <utility>

struct Foo {
    void process(int, std::string);
};

template <typename T>
struct Input { };

template <std::size_t N, typename T, typename ...Types>
struct Extract_type {
    using type = typename Extract_type<N - 1, Types...>::type;
};

template <typename T, typename ...Types>
struct Extract_type<0, T, Types...> {
    using type = T;
};

template <typename T, std::size_t N, typename R, typename ...Args>
typename Extract_type<N, Args...>::type extract(R (T::*)(Args...));

template <typename T, typename R, typename ...Args>
std::integral_constant<std::size_t, sizeof...(Args)> num_args(R (T::*)(Args...));

template <typename T>
struct Node {
    template <typename ...Args>
    Node(Input<Args>&&... args) 
    : Node(std::make_index_sequence<decltype(num_args<T>(&T::process))::value>{ }, std::forward<Input<Args>>(args)...)
    {}

    template <std::size_t ...Indices>
    Node(std::index_sequence<Indices...>, Input<decltype(extract<T, Indices>(&T::process))>...) {}
};


int main() {
    Node<Foo> b{ Input<int>{ }, Input<std::string>{ } };
}

http://coliru.stacked-crooked.com/a/da7670f80a229931


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接