1

The following program uses the append_list function with the rvalue reference signature, not the const reference one. Why?

#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 should be an lvalue reference to the bar field of the shmoo object. I don't see a "conversion sequence" here to make an rvalue reference out of it, but I admit there's a lot going on here I don't understand.

nmr
  • 16,625
  • 10
  • 53
  • 67

1 Answers1

0

In your example code, U is a forwarding reference and not an RValue reference:

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

Forwarding references behave differently than the usual template deduced types in that U will become the exact type of its input, matching both the CV-qualifiers and value category.

  • For PR values of type T, this produces U = T
  • For X-values of type T, this produces U = T&&
  • For L-values of type T, this produces U = T&

This differs from normal template matching where a deduced type from const U& would determine U = T.

When presented as an overload set with a function template that deduces its arguments via template matching, forwarding references will effectively be "greedy" -- since in most cases it will be an unambiguously better match for overload resolution.


With your example code:

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

schmoo->bar is passing a non-const lvalue reference of std::vector<int> into append_list.

During overload resolution, the compiler will always resolve the function with the most exact match (i.e. requires least number of conversions required). In the above overload, std::vector<int>& could be matched to const U& = const std::vector<T>& -- but this requires adding const, compared to matching U&& = std::vector&` which is an exact match.

As a result, the forward-reference overload is called.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
  • 2
    Excuse my nitpicking, but expressions can't have reference types. The ref-ness of the deduced template argument is determined from the *value category* of the expression. – HolyBlackCat Sep 24 '20 at 19:18
  • 1
    @HolyBlackCat I appreciate the nitpick -- it's much clearer to explain this specifically in terms of value categories. – Human-Compiler Sep 24 '20 at 19:23