2

I've been reading Effective Modern C++ and the following thing caught my attention:

In Item 28 Scott writes:

Together, these observations about universal references and lvalue/rvalue encoding mean that for this template

template<typename T> void func(T&& param);

the deduced template parameter T will encode whether the argument passed to param was an lvalue or an rvalue. The encoding mechanism is simple. When an lvalue is passed as an argument, T is deduced to be an lvalue reference. When an rvalue is passed, T is deduced to be a non-reference. (Note the asymmetry: lvalues are encoded as lvalue references, but rvalues are encoded as non-references.)

Can somebody explain why such encoding mechanism was chosen?

I mean if we will follow reference collapsing rules than usage of aforementioned template with rvalue yields rvalue reference. And as far as I can tell everything would work just the same if it were deduced as rvalue reference. Why is it encoded as non-reference?

Community
  • 1
  • 1
Stvad
  • 360
  • 2
  • 17
  • BTW, the term "universal reference" didn't catch on, and the committee chose to use [forwarding reference](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4164.pdf) instead. – Bo Persson Aug 29 '15 at 09:42
  • I think your question might be the same as this one http://stackoverflow.com/questions/32282705/a-failure-to-instantiate-function-templates-due-to-universal-forward-reference – aldr Aug 29 '15 at 09:51
  • See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm esp. #7. IIRC, Howard Hinnant once stated that introducing some `&&&` solely for forwarding references probably wasn't going to be accepted at that time (as a third reference kind). – dyp Aug 29 '15 at 10:05
  • OTOH, that doesn't seem to answer why `T` isn't deduced to be `A&&`, though. – dyp Aug 29 '15 at 10:12

2 Answers2

1

Let's say you need to pass param around. In fact, you need to store it for lifetime purposes. As is, you'd just use T:

template <typename T>
struct store { T val; };

template<typename T> void func(T&& param) {
    store<T> s{std::forward<T>(param)};
}

This works because if param got passed in by lvalue, T would be an lvalue reference type, and we're just copying the reference. If param got passed in by rvalue, we need to take ownership - T is a non-reference type, so we end up move-constructing into s.

The fact that I can just use T here as the template argument for store, instead of std::conditional_t<std::is_lvalue_reference<T>::value, T, std::remove_reference_t<T>> is probably not an accident.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    I don't understand your answer. If `store` is destroyed after `func` returns, then passing in rvalue references would work just as well as passing in lvalue references. If `store` is persisted after `func` returns, then lvalue references can also end up invalidated just as rvalue references can be. –  Aug 29 '15 at 10:28
  • 1
    I think the point was that it is just simpler to work afterwards with T than with T&& so providing we can achieve the same functionality with T it gets deduced as T. – Stvad Aug 31 '15 at 16:33
1

I think you're thinking the wrong way around. Instead of asking "why not always make T a reference type", look at it from the other side:

Suppose you have template <typename T> void f(T&&t).

Calling it as int i; f(i);, some T is needed such that T&& resolves to int&. What's the simplest T that can achieve that? It's int&.

Calling it as f(0);, some T is needed such that T&& resolves to int&&. What's the simplest T that can achieve that? It's int, not int&&.

int&& simply doesn't really have any advantages here.