I am new to templates and lambdas function and I am trying to understand what's happening in this piece of code:
function<void (const int &)> foo = [&](auto x) {
x = 42;
}
Here, I assume the compiler is able to tell from the auto keyword that x has type const int &. Therefore, the compiler should not allow the assignment x = 42.
However, this code compiles without error, which confuses me. Why does the compiler allow this? What did the compiler actually deduced for the auto keyword?
auto is never deduced to a reference. It always deduces to a decayed type.
When passed a const int & (or any reference to some type T), then it will be deduced to int (or the decayed type T). (Or to be more precise, when passed an expression of type T with any value category, it will always deduce to the decayed type T. Types of expressions never include references, which are instead translated into value categories.)
You can still assign the lambda, because it is possible to initialize an int from a const int& (or more precisely from an lvalue expression of type const int). This is what will happen when you call foo.
So you are assigning here to a local parameter object inside the function. The assignment is not visible outside the lambda.
If you want to deduce a reference, use auto&&. This however will then always deduce a reference. It also won't decay the type, so the const will be retained.
Related
#include<iostream>
using namespace std;
void foo(const long& a)
{
cout << "long version";
}
void foo( float& a)
{
cout << "float version" << endl;
}
int main()
{
int a = 3;
foo(a);
}
The example above will output "long version", however, according to C++ primer:
When we call an overloaded function that differs on whether a reference or pointer parameter refers or points to const, the compiler uses the constness of the argument to decide which function to call:
The program should output the version that does not contain the constness since they have the same arithmetic conversion precedence, but the program always chooses the one with const for some reason.
Could anyone explain to me why does the compiler choose the const version? Really appreciate your help :)
You are calling the function with an argument of type int, but the function is taking a reference to a long or float, which are different types, as parameter. You cannot bind a reference of one type to another type (up to const/volatile qualifiers and if they are not related through a class hierarchy).
What the compiler therefore tries to do is to create a temporary object of the parameter type initialized from the int argument by conversion (arithmetic conversion) and binds the reference to that.
Temporaries are rvalues, meaning that they cannot be bound to non-const lvalue references. Therefore, after creating a float object from the int passed to it, foos parameter would not be able to bind to it. Consequently the float overload is not viable.
The only overload left is the long one. This one is viable, because after creating the temporary long object, it can bind to a const lvalue reference.
And so the long version is called.
The quote you showed applies (for the most part) only if the argument and the parameters' types match (up to const) and the argument itself is an lvalue. I assume you missed this relevant context surrounding the quoted section.
You cannot call a function taking a non-const lvalue reference with a different type as argument (except if the types are related through a class hierarchy), nor can you call it with a rvalue as argument.
What you seem to be hoping for, is that the compiler will automagically do:
int a = 3;
foo(float(a));
The problem here, is that float(a) is effectively a temporary object, and that object happens to be const. Since foo takes a float&, the compiler cannot match the constant temp, to a non-const (aka mutable) argument. However, the compiler can do this:
int a = 3;
foo(long(a));
Since the argument for foo is const when using long. Given the compiler cannot use the float version, it will always pick the const long version instead.
Let's consider the following code snippet
void Test()
{
int x = 0;
int& rx = x;
int* px = &x;
auto apx = px; // deduced type is int*
auto arx = rx; // deduced type is int
}
One could draw an analogy from pointer types expecting that the deduced type of arx is int&, but it is int in fact.
What is the rule in Standard which governs that? What is the reason behind it?
Sometimes I get caught by it in a case like this:
const BigClass& GetBigClass();
...
auto ref_bigclass = GetBigClass(); // unexpected copy is performed
Use auto&:
auto& ref_bigclass = GetBigClass();
References are supposed to be transparent: any operation on them happens on the object they refer to, there is no way to 'get' reference itself.
UPD: This is covered in 7.1.6.4/6:
Once the type of a declarator-id has been determined according to 8.3, the type of the declared variable using the declarator-id is determined from the type of its initializer using the rules for template argument deduction.
And template argument deduction is defined in 14.8.2.1/3:
If template parameter type P is a reference type, the type referred to by P is used for type deduction.
P.S. Note that this is different for decltype: decltype(rx) will yield int& type (7.1.6.2/4).
The simplest way to think about it is comparing it to template argument deduction.
Given:
template<typename T>
void deduce(T) { }
If you call:
deduce(px);
then the template argument T will be deduced as int* and if you call
deduce(rx);
then T will be deduced as int, not int&
You get the same types deduced when using auto.
One could draw an analogy from pointer types expecting that the deduced type of arx is int&
You'd have to have a fairly confused model of the C++ language to make that analogy. Just because they are declared in syntactically similar ways, as Type# with a type and a modifier doesn't make them work the same way. A pointer is a value, an object, and it can be copied and have its value altered by assignment. A reference is not an object, it's a reference to some object. A reference can't be copied (copying it copies the referent) or altered (assigning to it alters the referent). A function that returns a pointer returns an object by value (the object in question being a pointer object), but a function that returns a reference (like your GetBigClass()) returns an object by reference. They're completely different semantics, trying to draw analogies between pointers and references is doomed to failure.
I got confused by the following perfect forwarding function, where the template parameter T can match rvalue or lvalue references:
template<typename T>
void foo(T&& t){
T::A; // intended error to inspect type
}
int main(){
std::vector<int> a;
std::vector<int> && b = std::move(a);
foo(b); // T is std::vector<int> &
foo(std::move(a)); // T is std::vector<int>
}
I dont understand why the template argument deduction of T in foo is so different in these two cases? Whats the fundamental difference and important what is t's type in function foo.
std::move(a) returns a rvalue reference and b is already a rvalue reference (but has a name).
Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?
Can anyone shine some light into this :-)
There is a special type deduction rule when && is used with templates.
template <class T>
void func(T&& t) {
}
"When && appears in a type-deducing context, T&& acquires a special
meaning. When func is instantiated, T depends on whether the argument
passed to func is an lvalue or an rvalue. If it's an lvalue of type U,
T is deduced to U&. If it's an rvalue, T is deduced to U:"
func(4); // 4 is an rvalue: T deduced to int
double d = 3.14;
func(d); // d is an lvalue; T deduced to double&
float f() {...}
func(f()); // f() is an rvalue; T deduced to float
int bar(int i) {
func(i); // i is an lvalue; T deduced to int&
}
Also, reference collapsing rule is a good read.
Check this out for a really good explanation:
perfect forwarding
If you think about the signature of your function, the type of the parameter is T&&. In your second example, T is deduced to vector<int>, that means that the type of the parameter to your function is vector<int>&&. So you are still passing by (rvalue) reference.
In the other case, you deduce T to vector<int>&. So the type of the argument is vector<int> & &&... or it would be, but references to references are not allowed. Reference collapsing takes over, and any double reference involving an lvalue reference become an lvalue reference. So you are passing by lvalue reference.
As far as b goes, this is a well known gotcha of rvalue references. Essentially, b's type is rvalue reference, but b itself still has a value category of lvalue. Think of it this way: b itself is a variable, that must live on the stack somewhere, and have an address. So it's an lvalue. This is precisely way calling std::forward when forwarding arguments is necessary. If you didn't do it, then they would always be forwarded as lvalue arguments.
I really recommend this Scott Meyers article: https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers. Read it carefully!
Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?
Yes, that's exactly it. It makes more sense if you think about rvalue reference function parameters: the caller is specifying that the function can do whatever it wants with the objects it gets. So from inside the function body, in order to make sure the code really can do whatever it wants with it, the parameter should be treated as an lvalue. That same argument can also be made for other rvalue references, including the b in your example, albeit to a lesser extent.
The expressions a and b are both lvalues, and the expression std::move(a) is an rvalue.
The deduction for the parameter T makes use of special reference collapsing rules so that the type of t is either an lvalue or an rvalue reference as needed to bind to the function call argument.
Can someone explain when rvalue references are preferred over const lvalue references when working as function parameters?
Background:
I was trying to pass a const pointer into a function. Since I have to consider the cases in which a local pointer is passed in and in which a temporary is passed in (say from a function call return), I have two choices: the parameter could either be declared as:
void foo(T const* const&); //const lvalue ref to const ptr
or
void foo(T const* &&); //rvalue ref to const ptr
But this rvalue reference cannot be bound to a local variable (which is of lvalue type. But I did remember Scott Meyers coined the term "universal reference" to refer to rvalue reference. This confuses me more.) So my question is, since the first declaration could deal with both cases, when would the second one using rvalue reference be preferred?
Note: In the first approach, the other forms
void foo(const const T* &);
void foo(const T* const&);
didn't work. I guess the reason is that in the latter two I was not consistent in the place where the const qualifiers are put into (please correct me if I'm wrong).
It is very rarely a good idea to pass a pointer by const &: at best it takes the same overhead, at worst it causes extremely complex pointer reseating logic to surprise readers of your code.
Take pointers by value -- T const* -- and things are more sane.
References to non-pointer values make more sense.
Universal references is a technique using rvalue and lvalue references in a type deduction context. It basically only applies when you have a type T&& being deduced from an expression -- in that context T can be X, X& or X const& (or other cv variants).
If T is X& or X const&, the rvalue reference to the lvalue reference collapses into a lvalue reference. It is an example of the standard committee being clever, and it allows auto&&x= based universal reference variables, and perfect forwarding code to be easy to write.
Remark: I wrote this answer under the assumption that T in your question represents some actual data type -- I've chosen int in my examples below.
Background: I was trying to pass a const pointer into a function. [...] I have to consider the cases in which a local pointer is passed in and in which a temporary is passed in (say from a function call return)
You didn't say what you mean by "const pointer". I will first assume you mean a pointer that is itself constant (i.e. the address to which it points cannot be changed).
According to your description, there are basically two ways you get such a pointer:
// Case 1 (what you call a local pointer -- this should be inside some
// function body):
int *const p = 0;
// Case 2, a function that returns a pointer; this is your rvalue case
// in contexts where f() is called and its return value used as a temporary:
int *f()
{ return 0; }
// Note: The temporary returned by this function isn't, strictly speaking,
// constant. It could be modified as long as it is alive. But from
// your description I take it that you have no intentions of doing so
// and/or regard temporaries as generally constant.
Now you can define a function that accepts these two cases as follows:
void g(int *const &arg)
{ }
You can apply this as g(p); to a constant, local pointer such as p defined earlier, as well as to a temporary g(f());. (You could, thirdly, apply it to a non-const local pointer as well, because going from non-const lvalue to const lvalue is never a problem.)
This function g has a function argument arg which is defined as a constant, lvalue reference to an int-pointer. It can bind to a constant (or indeed non-constant) local pointer (such as p) as well as a temporary, because constant lvalue references, unlike non-constant lvalue references, can do that.
Remark: It's not clear to me why, in this case, you need the function argument to be a reference at all. You could simply declare void g(int *const arg) (no ampersand) and do without a reference. Reasons include a) You cannot modify it anyway; b) In all real-world implementations, the reference will take just as much (or as little) space as the pointer itself, so there is no point in avoiding a copy.
Anyway. If you want you can also define a second version of g specifically for rvalue references:
void g(int *&& arg)
{ }
This can only be applied to the temporary, not to the local pointer, because the function argument is defined as an rvalue reference, which can bind to temporaries, but not to lvalues.
However, if by "const pointer" you actually mean a pointer-to-const, i.e. a pointer that can be changed to different addresses, but does not have the power to modify the value stored at those addresses, the declarations are a bit different. The keyword const must then be put before the asterisk, and for better clarity best before the type specifier int:
// Declare local pointer-to-const:
const int *p = 0;
// Function that returns a pointer-to-const:
const int *f()
{ return 0; }
A function that can accept these two would then be declared as:
void g(const int *const &arg)
{ }
The first const means we are talking about pointers-to-const, and the second const ensures we have a constant lvalue-reference, which can bind to both rvalues and lvalues. Note that this function can not modify what arg points to, because arg is declared as a constant lvalue reference. In the case where arg binds to the temporary, that is probably what we want anyway (as stated above). But in the case where the function is called as g(p);, we might actually want to modify the local pointer p from within g. If you want g to have this power, you need to define two versions of it:
void g(const int *&& arg)
{ /* Can bind to temporaries, but not modify them. */ }
void g(const int *& arg)
{ /* Can bind to local variables and modify what they point at */ }
Remark 1: Your original declaration const int *const &const is useless (and not even accepted by GCC). It would mean a "constant reference to a constant pointer to constant int", but since a reference to a constant pointer is implicitly itself a const-reference, the final const is superfluous (and not provided for by the Standard).
Remark 2: Universal references are not the same as rvalue references. Universal references are declared as T &&arg where T is a template parameter. Depending on what T refers to in each instantiation of the template, this may be an lvalue reference or an rvalue reference -- hence its "universal" character. This has nothing to do with your use case, anyway, though, since you are dealing with pointers T * here (even if we assume that T is a template parameter).
You do not want to differentiate between a temporary pointer and an lvalue pointer. That looks to me like something that's bound to fail rather sooner than later.
Universal Refs only apply in template functions like
template<class T> void foo(T && fwdref);
Note that a "universal ref" is not the same as an rvalue ref.
Can you please explain me the difference between mechanism of the following:
int function();
template<class T>
void function2(T&);
void main() {
function2(function()); // compiler error, instantiated as int &
const int& v = function();
function2(v); // okay, instantiated as const int&
}
is my reasoning correct with respect to instantiation?
why is not first instantiated as const T&?
Thank you
Because function returns a non-const value. Only objects can be const, because they store some state that could be modified if it weren't const. What you return there is not an object, but a pure value. Conceptually, they are not modifiable (like enumeration constants, for example), but they are not const qualified (like, again, enumeration constants).
I think that you might be confused between rvalues and the const qualifier. function returns a non-const rvalue temporary of type int, so the compiler deduces T to be int, as it should. As you point out, you can bind a temporary to a const ref (c++03 12.2/5), but the compiler will not add cv qualifiers to make a function call well formed. Since you can't control the template function, there are two ways around this (in addition to the solution you posted).
(1) Explicit template parameters: function2<const int>(function())
(2) cv qualify return: const int function();
Both of these solutions are well formed. (1) seems the better solution, IMHO, since (2) is unconventional and silly.
Edit: Actually, the deduced type can be more cv-qualified than the argument for a ref template argument, but only if type deduction would otherwise fail (c++03 14.8.2.1/3). In this case, type deduction doesn't fail, but results in a malformed function call (SFINAE does not apply, because the template function specialization itself is not malformed).
If the intent of the template author was to not modify the argument, it should be declared as a const reference argument, so this may be a bug in the template library, or it may modify the argument, in which case what you are doing will fail where the function attempts to modify the argument.
Edit: As FredOverflow points out, non-class rvalues are always cv unqualified by the standard 3.10/9. So (2), which works under gcc 4.3, is actually a compiler bug (gcc <4.5, according to FredOverflow).
In this line
function2(function());
after function2 returns, the argument that passes to it might have its value change, but since function() returns and it's just assigned to a temporary variable, but what would happen to this temporary variable after it goes out of scope is the problem, that's why the compiler complaints.
To compile the first call, it is necessary to define function2 with T&& parameter - this is rvalue, reference to temporary object. In the second call, v is lvalue reference, it is OK. If your compiler doesn't support rvalue references, the first call may be compiled only with T parameter, without reference.