【问题标题】:Is this variadic template argument deduction correct?这个可变参数模板参数推导正确吗?
【发布时间】:2011-06-17 10:51:20
【问题描述】:

我一直在试验可变参数模板和参数转发。我想我发现了一些不一致的行为。

为了说明,这个程序:

#include <iostream>
#include <typeinfo>
#include <tuple>
#include <cxxabi.h>

template <typename... Args> struct X {};

struct A {
    A () {}
    A (const A &) {
        std :: cout << "copy\n";
    }
};

template <typename T> const char * type_name () {
    return abi :: __cxa_demangle (typeid (T) .name (), 0, 0, NULL);
}

void foo () {}

template <typename... Args>
void foo (const A &, Args ... args) {
    std :: cout << type_name <X <Args...>> () << "\n“;    foo (args...);
}

int main () {
    foo (A(), A());
}

输出以下内容:

X<A, A>
copy
X<A>

尽管 foo 的模板特化有一个 const 引用第一个参数,但可变参数是按值传递的,因为模板类型被推断为非引用类型,正如 X&lt;A&gt; 输出所示。

所以,我咨询Thomas Becker

template<typename T>
void foo(T&&);

这里适用以下情况:

  1. 当在 A 类型的左值上调用 foo 时,T 解析为 A& 并且 因此,通过参考折叠 上面的规则,参数类型 有效地变成了 A&。

  2. 当在 A 类型的右值上调用 foo 时,T 解析为 A,并且 因此参数类型变为 A&&。

试试这个:

template <typename... Args>
void foo (const A &, Args && ... args) {
    std :: cout << type_name<X<Args...>>() << "\n";
    foo (args...);
}

哪些输出:

X<A, A>
X<A&>

现在我被难住了。这里有 foo 的三个调用。在我看来,main() 应该推断出foo&lt;A,A,A&gt;(A&amp;&amp;,A&amp;&amp;,A&amp;&amp;)(因为 A() 是一个未命名的右值,因此是一个引用),它重载解析为foo&lt;A,A,A&gt;(const A&amp;,A&amp;&amp;,A&amp;&amp;)。这反过来推导出foo&lt;A,A&gt;(A&amp;&amp;,A&amp;&amp;)等等。

问题是:为什么X&lt;A,A&gt; 没有引用As 而X&lt;A&amp;&gt; 有引用A

这会导致问题,因为我不能在递归中使用std::forward

【问题讨论】:

  • 您确定您的代码与您的输出相符吗?我得到X&lt;A&gt; X&lt;&gt;,或者A()s 在mainX&lt;A, A&gt; copy X&lt;A&gt; X&lt;&gt; 中的数量增加。你怎么能把X&lt;A&amp;&gt; 作为最后一行,不管有没有和号,这是一个谜。无论如何都应该是X&lt;&gt;

标签: templates reference c++11 forwarding variadic


【解决方案1】:

首先,我假设您粘贴了错误的主要内容,而正确的主要内容是:

 int main () {
        foo (A(), A(), A());
 }

希望我猜对了,否则这篇文章的其余部分无效:)
其次,我不太擅长标准术语,所以我希望以下内容不会太不准确。

我经常觉得理解可变参数模板发生的事情的最简单方法就是自己生成编译器将有效执行的操作。

例如你的第一次尝试

void foo (const A &amp;, Args ... args)

实际上会被编译器解压成类似这 3 个函数的东西:

void foo3 (const A &amp; a, A a1, A a2)
void foo2 (const A &amp; a, A a0)
void foo1 (const A &amp; a)

当在 main 中调用 foo3(A(), A(), A()); 时,编译器将进行优化,只使用默认构造 a1 和 a2 而不是默认构造和复制。但是,当在 foo3 中调用 foo2 时,编译器无法再优化,因此在调用 foo2(a1, a2) 时,必须对 foo2 中的参数 a0 进行复制构造。我们可以在痕迹中看到 a0 的复制构造(“复制”)

顺便说一句,我不明白为什么您通过 const ref 传递第一个元素,而其余的则通过值传递。为什么不通过 const-ref 传递所有内容?

void foo (const A &amp;, const Args&amp; ... args)

好的,现在关于您第二次尝试使用 rvalue-ref :

void foo (const A &amp;, Args &amp;&amp; ... args)

当在 main 中调用 foo3(A(), A(), A()); 时,A() 是一个右值,因此适用此规则:

当 foo 被一个右值调用时 键入 A,然后 T 解析为 A,并且 因此参数类型变为 A&&。

所以 foo3 看起来像这样:

void foo3 (const A &, A && a1, A && a2) {
    std :: cout << type_name<X<A, A>>() << "\n";
    foo2 (a1, a2);
}

但是这里要小心。 a1 和 a2 不再是右值。它们有一个名字,因此它们是左值。

所以现在在 foo2 内部,这条规则适用:

当 foo 被一个左值调用时 输入 A,然后 T 解析为 A& 和 因此,通过参考折叠 上面的规则,参数类型 有效地变成了A&。

所以 foo2 中的 a0 类型实际上是 A&,这就是为什么我们可以在跟踪中看到 X&lt;A&amp;&gt;

要使用完美转发,我认为你需要放弃这个奇怪的假设:

这会导致问题,因为我不能 在递归中使用 std::forward。

我不明白为什么。以下代码应该是正确的:

void foo(A&&, Args&&... args)
{
   std::cout << type-name<X<Args...>> () << "\n";
   foo(std::forward<Args>(args...));
}

当为递归调用 foo() 时,std::forward 会将每个 args... 从左值再次转换为右值,因此推导是正确的。

【讨论】:

    猜你喜欢
    • 2013-09-14
    • 1970-01-01
    • 1970-01-01
    • 2012-10-26
    • 2020-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多