这个可变参数模板参数推导是否正确?

时间:2011-06-17 10:51:20

标签: templates reference c++11 forwarding variadic

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

为了说明这个程序:

#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引用第一个参数,但是variadic参数是按值传递的,因为模板类型被推导为非引用类型,如X<A>输出所示。

所以,我咨询Thomas Becker

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

以下适用:

     
      
  1. 当在类型A的左值上调用foo时,则T解析为A&amp;和   因此,参考崩溃   上面的规则,参数类型   实际上变成了A&amp;。

  2.   
  3. 当在类型A的右值上调用foo时,则T解析为A,然后   因此论证类型变为A&amp;&amp;。

  4.   

试试这个:

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<A,A,A>(A&&,A&&,A&&)(因为A()是一个未命名的右值,因此是一个引用),它超载解析为foo<A,A,A>(const A&,A&&,A&&)。这反过来推断foo<A,A>(A&&,A&&)等等。

问题是:X<A,A>如何有非参考AX<A&>有参考A

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

1 个答案:

答案 0 :(得分:2)

首先,我假设您粘贴了错误的主页,正确的是:

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

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

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

例如你的第一次尝试

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

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

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

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

顺便说一下,我不明白为什么你用const ref传递第一个元素,但是其余的值是通过值传递的。为什么不通过const-ref传递所有内容?

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

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

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

在主要内容中调用foo3(A(), A(), A());时,A()是左值,因此适用此规则:

  

当foo在rvalue上调用时   输入A,然后T解析为A,和   因此论证类型变为A&amp;&amp;。

所以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&amp;和   因此,参考崩溃   上面的规则,参数类型   实际上变成了A&amp;。

所以f02里面的a0类型实际上是A&amp;这就是为什么我们可以在痕迹中看到X<A&>

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

  

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

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

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

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