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

发布于 2024-11-15 16:14:46 字数 2060 浏览 2 评论 0原文

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

为了说明这一点,该程序:

#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<; A> 输出表示。

因此,我咨询了 Thomas Becker

模板<类型名称 T>
无效 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&>

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

问题是:为什么 X 有非引用 AX 有引用 >一个?

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

I've been experimenting with variadic templates and argument forwarding. I think I've found some inconsistent behaviour.

To illustrate, this program:

#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());
}

Outputs the following:

X<A, A>
copy
X<A>

Even though the template specialisation of foo has a const reference first argument, the variadic arguments are passed by value because the template types are deduced as non-reference types, as the X<A> output indicates.

So, I consult Thomas Becker:

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

Here, the following apply:

  1. When foo is called on an lvalue of type A, then T resolves to A& and
    hence, by the reference collapsing
    rules above, the argument type
    effectively becomes A&.

  2. When foo is called on an rvalue of type A, then T resolves to A, and
    hence the argument type becomes A&&.

And try this:

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

Which outputs:

X<A, A>
X<A&>

Now I'm stumped. There are three invocations of foo here. In my head, main() should deduce foo<A,A,A>(A&&,A&&,A&&) (because A() is an unnamed rvalue, hence a reference) which overload-resolves to foo<A,A,A>(const A&,A&&,A&&). This in turn deduces foo<A,A>(A&&,A&&) and so on.

Question is: how come X<A,A> has non-reference As but X<A&> has a reference A?

This causes a problem because I can't use std::forward in the recursion.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

つ可否回来 2024-11-22 16:14:46

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

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

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

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

例如,您的第一次尝试

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

实际上会被编译器解包为类似于这 3 个函数:

void foo3 (const A &, Args ... args) 。 a、A a1、A a2)
void foo2 (const A & a, A a0)
void foo1 (const A & a)

当在 main 中调用 foo3(A(), A(), A()); 时,编译器会进行优化,只是默认构造 a1 和 a2 而不是默认构造和复制。然而,当在 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)

当在 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

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

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

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

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

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

First, I assume you pasted the wrong main and the correct one is :

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

I hope I guessed right, otherwise the rest of this post is invalid :)
Second, I'm not really good with standard terminology, so I hope the following is not too imprecise.

I often feel that the simplest way to understand what happen with variadic template is just to generate yourself what the compiler will effectively do.

For example your first try

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

will actually be unpack by the compiler to something like those 3 functions :

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

When calling foo3(A(), A(), A()); in the main the compiler will do an optimization and just default-construct a1 and a2 instead of default-construct and copy. However when calling foo2 inside foo3 the compiler cannot optimize anymore hence when calling foo2(a1, a2) the parameter a0 inside foo2 must be copy-constructed. And it's the copy-construction of a0 that we can see in the traces ("copy")

By the way, I don't understand why you pass the first element by const ref but the rest by value. Why not pass everything by const-ref ?

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

OK, now about your second try with rvalue-ref :

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

When calling foo3(A(), A(), A()); in the main, A() is an rvalue so this rule applies :

When foo is called on an rvalue of
type A, then T resolves to A, and
hence the argument type becomes A&&.

So foo3 looks like this:

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

But be careful here. a1 and a2 are NOT rvalue anymore. They have a name hence they are lvalue.

So now inside foo2, this rule applies :

When foo is called on an lvalue of
type A, then T resolves to A& and
hence, by the reference collapsing
rules above, the argument type
effectively becomes A&.

So a0 type inside foo2 will effectively be A& and that's why we can see X<A&> in the traces.

To use perfect forwarding I think you need to give-up this weird assumption :

This causes a problem because I can't
use std::forward in the recursion.

I don't understand why. The following code should be correct :

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

When calling foo() for the recursion , std::forward will turns each args... from lvalue to rvalue again and so the deduction will be correct.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文