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,307
  • 10
  • 51
  • 63

1 Answers1

1

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
  • 63,700
  • 7
  • 105
  • 170
Human-Compiler
  • 10,027
  • 27
  • 49
  • 1
    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