【问题标题】:Variadic Template Constructor to build Parent可变参数模板构造函数来构建父级
【发布时间】:2014-05-08 18:48:13
【问题描述】:

我试图从一堆我知道接口的类中继承,但是它们可能有非常不同的构造函数。为此,我决定在派生类构造函数中使用可变参数模板,以便它可以获取最终馈送到父类的任意参数。

我的代码如下:

#include <iostream>

struct A {
    A(int) { std::cout << "This is int\n"; }
    A(unsigned) { std::cout << "This is unsigned\n"; }
};

struct B {
    B(char) { std::cout << "This is char\n"; }
};

template <typename T>
struct C : public T {
    template <typename... Args>
    C(double, Args&&... params) : T(std::forward<Args>(params)...) { std::cout << "This is double\n"; }
    // But what about this?
    // C(Args&&... params, double) : T(std::forward<Args>(params)...) { std::cout << "This is double\n"; }
};

int main() {
    C<A> p(1.0, 1);
    C<A> r(1.0, 1u);
    C<B> q(1.0, 'c');

    // Which would work as following
    // C<A> p(1,   1.0);
    // C<A> r(1u,  1.0);
    // C<B> q('c', 1.0);
    return 0;
}

我的问题是:

  • 此代码正确吗?这是我第一次尝试在构造函数中使用可变参数模板,所以如果我遗漏了什么,我很想听听您的意见。
  • 我希望将子类C 的参数留在最后,但据我了解这是不可能的,因为在构造函数中不允许指定模板参数,在这种情况下可变参数会吞下所有参数,而没有给实际的孩子留下任何参数。有没有办法做到这一点?

【问题讨论】:

  • 我的直觉是,这不是解决这个问题的最佳方法。你不能只接受适量的论点并做这样的事情吗:stackoverflow.com/questions/120876/….
  • @maxywb:但他不知道有多少个参数才是正确的参数个数,因为他是从模板参数派生的。

标签: c++ templates constructor variadic-templates argument-passing


【解决方案1】:

14.8.2.1 从函数调用中推导出模板参数

1 [...] 对于没有出现在 parameter-declaration-list 末尾的函数参数包,参数包的类型是非推导上下文。

5 [...] [ 注意: 如果一个 template-parameter 没有用于函数模板的任何函数参数中,或者仅用于一个非推导的上下文,其对应的 template-argument 不能从函数调用中推导出来,并且 template-argument 必须明确指定。 — 尾注 ]

14.8.2.5 从类型推导模板参数

如果模板参数仅用于非推导上下文且未明确指定,则模板参数推导失败。

因此,正如您所观察到的,应该明确指定 Args...,但由于这对于构造函数来说是不可能的,因此这是行不通的。

可以工作的是将用于基本构造函数的所有参数打包在一个元组 (live example) 中,然后是派生类的剩余参数:

template <typename T>
class C : public T
{
    template<typename... A, size_t... I>
    C(std::tuple<A...>&& a, sizes<I...>, double x) :
        T(std::get<I>(std::move(a))...) { std::cout << "This is double\n"; }

public:
    template<typename... A>
    C(std::tuple<A...>&& a, double x) :
        C(std::move(a), idx<sizeof...(A)>{}, x) { }
};

用作

int main() {
    C<A> p(std::forward_as_tuple(1),      1.0);
    C<A> r(std::forward_as_tuple(1u, 2.), 1.0);
    C<B> q(std::forward_as_tuple('c', 0), 1.0);
}

我让你的例子更丰富一点:

struct A {
    A(int) { std::cout << "This is int\n"; }
    A(unsigned, double) { std::cout << "This is unsigned, double\n"; }
};

struct B {
    B(char, int) { std::cout << "This is char, int\n"; }
};

和剩下的样板

template<size_t... I> struct sizes { using type = sizes<I...>; };

template<size_t N, size_t K = 0, size_t... I>
struct idx_t : idx_t<N, K+1, I..., K> {};

template<size_t N, size_t... I>
struct idx_t<N, N, I...> : sizes<I...> {};

template<size_t N>
using idx = typename idx_t<N>::type;

直到std::integer_sequence 可用。

如果您发现 std::forward_as_tuple 的名称太长,则不难定义您自己的名称,例如pack:

template<typename... A>
constexpr std::tuple<A&&...>
pack(A&&... a) { return std::tuple<A&&...>{std::forward<A>(a)...}; }

即便如此,语法

C<B> q(pack('c', 0), 1.0);

确实增加了一些开销,但我发现最终用户很自然地知道正在发生的事情。如果将更多参数添加到派生类构造函数中,那么扁平化到C&lt;B&gt; q('c', 0, 1.0); 不仅几乎不可能实现,而且对用户来说也是模棱两可和困惑的。真正酷的是像

这样的语法
C<B> q('c', 0; 1.0);

(例如,在函数中分隔输入/输出参数),但我还没有在任何语言中看到这一点,只在教科书中看到过。

【讨论】:

    猜你喜欢
    • 2016-09-02
    • 2018-05-18
    • 2016-01-02
    • 2015-05-06
    • 2011-09-05
    • 2018-02-03
    • 2014-04-21
    • 2019-01-29
    • 1970-01-01
    相关资源
    最近更新 更多