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?
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.
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.
Related
Lets consider ordinary perfect forwarding:
class Test
{
public:
Test() = default;
Test(Test const&) { std::cout << "copy\n"; }
Test(Test&&) { std::cout << "move\n"; }
};
void test(Test)
{ }
template <typename T>
void f(T&& t)
{
test(std::forward<T>(t));
}
int main()
{
std::cout << "expect: copy\n";
Test t;
f(t);
std::cout << "expect: move\n";
f(Test());
return 0;
}
So far everything is fine. But if we now introduce a function pointer I get the problem that I seem not to be able to declare universal (forwarding) references:
decltype(&f<Test>) ptr = &f<Test>;
// above produces ordinary r-value references
// making below fail already on compilation:
ptr(t);
Template function pointers get problematic as well:
template <typename T>
void(*ptr)(T&& t) = &f<T>; // again resolved to r-value reference
At first, they resolve to pure r-value reference as well, additionally they define a bunch of pointers instead of a single one, making the approach unusable within class scope:
class C
{
template <typename T>
void(*ptr)(T&& t); // fails (of course...)
};
So question now is: Is it possible at all to have indirect perfect forwarding via function pointers?
Admitted, already fearing the answer is 'no' (and currently falling back to l-value references), but still in hope of having overlooked something somewhere...
A forwarding reference is not only a hypothetical concept. It is the name given to a specific kind of rvalue reference which has special deduction rules in template argument deduction.
Specifically according to [temp.deduct.call]/3:
A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.
Except for this special rule (and one other in [temp.deduct.type]), a forwarding reference behaves just like any other rvalue reference.
In particular when providing a template argument for T, there is no template argument deduction happening and substitution is performed directly. In substitution the usual reference collapsing rules are applied.
So f<Test> will yield the function parameter Test&& which is a rvalue reference and f<Test&> will yield the function parameter Test& (collapsed from && being applied to Test&).
These are also the template arguments that would be deduced for a rvalue argument and a lvalue argument in a function call without explicit template arguments. If the reference was not a forwarding reference, then T could never be deduced to a reference and the function parameter would always be a rvalue reference after substitution. The special adjustment of A mentioned in the quote allows T to be deduced to an lvalue reference type, so that the collapsing rules will result in an lvalue reference function parameter as well.
Forwarding references can only exist in templates. A function or specialization of a template cannot have a forwarding reference. They are not somehow a different category of reference from rvalue/lvalue references. They work in templates only by deducing to different types for different value categories of arguments and produce distinct specializations for each value category.
So, since a function pointer must point to a function, not a function template, which of the two specializations for the value category of the argument to choose, has to be decided when taking the function pointer.
If deduction of any kind is expected, then a function pointer cannot offer that. Instead a lambda or functor type should be used which can perform deduction and can choose the function or function template specialization to call based on value category.
The first example uses template argument deduction and reference collapsing rules to compute the correct type. When you explicitly supply the template arguments this disables deduction and the supplied type is substituted directly, resulting in the rvalue reference.
Perfect forwarding requires the template argument to be deduced. A function pointer can only point to a single function, not to a set of overloads, and not to a function template.
You can get a function pointer to either the r-value reference instantiation:
decltype(&f<Test&&>) ptr1 = &f<Test&&>;
ptr1(Test()); // output: move
or the l-value instantiation:
decltype(&f<Test&>) ptr2 = &f<Test&>;
ptr2(t); // output: copy
But not to both at the same time. ptr1 and ptr2 are pointers to functions of different type.
When you want something to hold not just a single function but more than one overload you can use a type with member functions. For example with a lambda expression:
auto fw = [](auto&& t){ test(std::forward<decltype(t)>(t)); };
fw(t); // output: copy
fw(Test()); // output: move
Background of the question is a mis-reading of Scott Meyer's article about forwarding references (called 'universal references' there).
The article gave the impression of such forwarding references existing as a separate type in parallel to ordinary l-value and r-value references. This is not the case, though, instead it is a hypothetical construct to explain special deduction rules that exist if template parameters serve as function parameter types.
As such a type does not exist in consequence it is not possible to declare a function pointer using that type, proving indirect perfect forwarding via function pointer impossible to realise.
Here for example, b is of type int& but f(b) resolves to f(int):
#include <type_traits>
template <typename T>
void f(T arg) {
static_assert(std::is_reference<T>::value); // fails
}
void g() {
int a = 5;
int& b = a;
f(b);
}
I know the standard dictates it - I'm asking, why? What goes wrong (or - becomes 'surprising') if the reference isn't dropped?
There are two things at play here.
What if you, as the author of f, wants to write a template function where the user is required to copy/move a parameter into the function?
In non-template code, that prototype would look like this: void f(SomeType st);. Any caller of f has no choice but to copy/move into the parameter. Whether they have an object, a reference to an object, or something else. st shall be an object separate and distinct from the rest.
Template argument deduction is intended to look like the non-template version where possible. So if you have template<typename T> void f(T t);, and you call it with f(some_object), that should work exactly like the untemplated version.
The other thing at play here is that reference variables are supposed to just be a different name for some object. That is, a and b, should, all things being equal, behave identically. So f(a) ought to do the same thing as f(b).
The rules of template argument deduction favor both of these. It keeps references behaving exactly like the object, and it allows the template function to behave like an equivalently-defined non-template version.
If you want a reference type, you must forgo template argument deduction and specify it directly: f<decltype((b))>(b). Note the use of double-parentheses here; that's important for getting decltype to be a reference.
According to the rules of the language, f(b) is not calling f with an expression of type int&. It is calling f with an lvalue of type int. The same would also occur if you did f(a).
In general, when a reference variable is mentioned by name, it is an expression that is an lvalue of the referenced type. The fact that the variable itself is a reference is not visible to the type system.
Thus, f never deduces T to have reference type. From f's point of view, the argument is never actually a reference.
What goes wrong (or - becomes 'surprising') if the reference isn't dropped?
Consider following function:
template <typename T>
void f(T arg) {
something = std::move(arg);
// ...
f(lvalue); // copies
f(str::move(lvalue)); // moves
The intention is to copy from an lvalue, and move from an rvalue.
If T deduced to a reference, then lvalue argument would be moved from, which is in this case undesirable.
I don't want to copy the argument.
If you're writing the function, then specify a reference parameter using & symbol, and not an object parameter. That way the argument can never be copied into the call (you can still copy inside the function).
If you're calling a function that accepts an object parameter, then pass an rvalue and not an lvalue. That way you will copy only if the move is a copy, and there is no copy elision involved.
Doesn't make sense to me that the template type deduction rules decide for me that I do
You decided to use T. You should instead decide to use T&, T&& or const T& if you want a reference.
Suppose you have a template argument T.
What are the differences between
add_cv_t<T> and const volatile T
add_const_t<T> and const T
add_volatile_t<T> and volatile T
add_lvalue_reference_t<T> and T&
add_rvalue_reference_t<T> and T&&
add_pointer_t<T> and T*?
Why should I use add_rvalue_reference_t<T> instead of T&& for example. Are there any rules when to choose which?
add_cv_t<T> and const volatile T
add_const_t<T> and const T
add_volatile_t<T> and volatile T
No difference; the definition of add_const<T>::type is just T const, for example.
add_lvalue_reference_t<T> and T&
add_rvalue_reference_t<T> and T&&
T& and T&& are ill-formed when T is cv void, but these templates are well-formed, just giving the original type back.
add_pointer_t<T> and T*?
add_pointer_t<T> is equivalent to std::remove_reference<T>::type*. That is, if T is a reference type, it gives a pointer to the referenced type. On the other hand, T* will be ill-formed since you cannot have a pointer to a reference.
Which should you use?
In general, the alias templates can be used to prevent deduction of T. Of course, that means that if you want deduction, you should avoid them.
The alias templates can be used as template template arguments to a template that takes a type transformation as a parameter.
The alias templates that differ in behaviour from alternatives like T* are useful in generic code since they "do the right thing". For example, if T is deduced from an argument of type T&&, then T* does the wrong thing when the argument is an lvalue, since it tries to declare a pointer to an lvalue reference. But std::add_pointer_t<T> will give a pointer to the actual type of the argument.
According to what I see in STL source:
add_cv_t<T> and const volatile T- no difference
add_const_t<T> and const T - no difference
add_volatile_t<T> and volatile T - no difference
add_lvalue_reference_t<T> and T& - there is difference for example if T is non referenceable type void. add_lvalue_reference_t<void>::type = void and void& = compile-time error
add_rvalue_reference_t<T> and T&& - the same as above
add_pointer_t<T> and T* - difference when T is reference, because there is no such thing as pointer to reference. add_pointer_t<T> is equivalent to std::remove_reference<T>::type*
In most cases, std::add_rvalue_reference_t<T> is equivalent to T&&. However, reference collapsing rules and the-rules-that-dictate-which-types-are-referenceable may have your code misbehave if not taked into account.
There are, however, some cases where the type static member type will be different due to T being a non-referenceable type. For example std::add_rvalue_reference_t<void> resolves to void, and (taking another template you mentioned as an example) std::add_pointer_t<T&> resolves to T* (if you want to invoke chaos, the required ritual is std::add_pointer_t<std::add_rvalue_reference_t<void>> :))
Respecting to uses, it may be used as a template template parameter to do some funky black magic. Anyway, stuff like std::is_rvalue_reference_t<T> or std::remove_reference_t<T> is usually more commonly used when manipulating the type's reference attributes.
I saw possible implementations for std::remove_reference as below
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
Why is it that there are specializations for lvalue and rvalue reference? Won't the general template itself be sufficient and remove the reference? I'm confused here because in the T& or T&& specialization if I try to use ::type I should still get T& or T&& respectively right?
Could you explain how, why we cast to remove_reference<t>::type&& in move? (is it because that the parameter is named so it will be treated as an lvalue inside the move function?).
Also, could you point out a way whereby I can find out and print what the type is? for e.g if its an rvalue of type int then I should be able to print out that int&& was passed? (I've been using std::is_same to check but manually.)
Thank you for your time.
why is it that there are specializations for lvalue and rvalue reference?
If only the primary template existed, then doing:
remove_reference<int&>::type
Would give you:
int&
And doing:
remove_reference<int&&>::type
Would give you:
int&&
Which is not what you want. The specializations for lvalue references and rvalue references allow stripping the & and the &&, respectively, from the type argument you pass.
For instance, if you are doing:
remove_reference<int&&>
The type int&& will match the pattern specified by the T&& specialization, with T being int. Since the specialization defines the type alias type to be T (in this case, int), doing:
remove_reference<int&&>::type
Will give you int.
could you explain how, why we cast to remove_reference<t>::type&& in move?
That's because if move() were defined as follows:
template<typename T>
T&& move(T&& t) { ... }
// ^^^
// Resolves to X& if T is X& (which is the case if the input has type X
// and is an lvalue)
Then the return type will be X& if the argument of move() is an lvalue of type X (that's how so-called "universal references"). We want to make sure that the return type is always an rvalue reference.
The purpose of move() is to give you back an rvalue, no matter what you pass in input. Since a function call for a function whose return type is an rvalue reference is an rvalue, we really want move() to always return an rvalue reference.
That's why we do remove_reference<T>::type&&, because appending && to a non-reference type is always guaranteed to yield an rvalue reference type.
Also could you point out a way whereby I can find out and print what the type is?
I'm not sure what you mean by "print" here. There is no portable way I know of converting the name of a type to a string (no matter how you obtain that type).
If your goal is to make sure that an rvalue was passed, on the other hand, you could use a static assertion like so:
#include <type_traits>
template<typename T>
void foo(T&&)
{
static_assert(!std::is_reference<T>::value, "Error: lvalue was passed!");
// ...
}
Which relies on the fact that when an lvalue of type X is being passed, T will be deduced to be X&.
You could also use an equivalent SFINAE-constraint, if you only want to produce a substitution failure:
#include <type_traits>
template<typename T, typename std::enable_if<
!std::is_reference<T>::value>::type* = nullptr>
void foo(T&&)
{
// ...
}
When you treat some type as template parameter, compiler searches for the most "specialized" specialization. If you pass a int&& to this template, compiler use remove_reference<T&&> version. General specialization don't give you what you want - if you pass int&& to general specialiation, type will be int&&
If you want to print type, use typeid(some_type).name()
I understand that, given an expression initializing a forwarding/universal reference,lvalues are deduced to be of type T& and rvalues of type T (and not T&&).
Thus,to allow only rvalues, one need to write
template<class T, enable_if<not_<is_lvalue_reference<T> >,OtherConds... > = yes>
void foo(T&& x) {}
and not,
template<class T, enable_if<is_rvalue_reference<T>,OtherConds... > = yes>
void foo(T&& x) {}
My question is , why for forwarding references, rvalues are deduced to be of type T and not T&& ? I guess, if they are deduced as T&& then also same referencing collapsing rule works as T&& && is same as T&&.
Because at the time, deducing rvalue A arguments as A&& instead of A was seen as an unnecessary complication and departure from the normal rules of deduction:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
We really didn't even know if we could get one exception of the deduction rules (for the lvalue A case), and it never even occurred to us that we would dare ask for two exceptions. To do so, the benefit would have had to have been: It makes what is impossible, possible.
After all, without the single special deduction rule for the lvalue case, perfect forwarding is impossible, as N1385 so aptly demonstrated.
Even with today's hindsight, adding another special deduction rule so that the client can avoid having to negate a template constraint, does not seem like a very high benefit/cost ratio. Especially compared to the benefit/cost ratio we were shooting for in 2002.