为什么这个重载解析 select 带有右值引用的签名?

Why does this overload resolution select the signature with the rvalue reference?

以下程序使用带有右值引用签名的 append_list 函数,而不是 const 引用签名。为什么?

#include <stdio.h>

#include <iterator>
#include <memory>
#include <vector>

class foo {
public:
    std::vector<int> bar{1};
};

template<typename T, typename U>
static void append_list(T &t, const U &u) {
    t.insert(t.end(), u.begin(), u.end());
}

template<typename T, typename U>
static void append_list(T &t, U &&u) {
    printf("move\n");
    std::move(u.begin(), u.end(), std::back_inserter(t));
}

int main() {
    auto shmoo = std::make_shared<foo>();
    std::vector<int> baz{2};
    append_list(baz, shmoo->bar);
}

https://godbolt.org/z/jdbEd1

AFAICS shmoo->bar 应该是对 shmoo 对象的 bar 字段的左值引用。我在这里没有看到一个“转换序列”来制作右值引用,但我承认这里有很多我不明白的地方。

在您的示例代码中,U 是转发引用而不是 RValue 引用:

template<typename T, typename U>
static void append_list(T &t, U &&u) {
//                            ^~~~~

转发引用的行为不同于通常的模板推导类型,因为 U 将成为其输入的 exact 类型,匹配 CV-qualifiers 和值类别。

  • 对于 T 类型的 PR 值,这会产生 U = T
  • 对于 T 类型的 X-values,这会产生 U = T&&
  • 对于 T 类型的 L-values,这会产生 U = T&

这不同于正常的模板匹配,在正常模板匹配中,从 const U& 推导的类型将确定 U = T

当作为 重载集 呈现时,带有通过模板匹配推导其参数的函数模板,转发引用将有效地“贪婪”——因为在大多数情况下它将是明确更好的重载解析匹配


使用您的示例代码:

int main() {
    auto shmoo = std::make_shared<foo>();
    std::vector<int> baz{2};
    append_list(baz, shmoo->bar);
}

schmoo->barstd::vector<int> 的非 const 左值引用传递给 append_list.

在重载解析期间,编译器将始终解析具有最精确匹配的函数(即需要最少数量的转换)。在上面的重载中,std::vector<int>& 可以匹配到const U& = const std::vector<T>&——但这需要添加const,相比于匹配U&& = std::vector&` 完全匹配。

因此,forward-reference 重载被调用。