为什么此重载分辨率选择带有右值引用的签名?

问题描述

以下程序将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,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将成为其输入的确切类型,与CV限定词和值类别都匹配。

  • 对于类型为T的PR值,这将产生U = T
  • 对于类型为T的X值,将产生U = T&&
  • 对于类型为T的L值,这将产生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->barconst的非std::vector<int>左值引用传递到append_list中。

在重载解析期间,编译器将始终以最匹配的方式解析该函数​​(即,所需的转换次数最少)。在上述重载中,std::vector<int>& 可以匹配到const U& = const std::vector<T>&,但是与匹配{{1 }} std :: vector&`完全匹配。

因此,将调用前向引用重载。