C++ Templates Question - c++

Can someone explain the output of the following code?
#include <iostream>
template <class T>
void assign(T& t1, T& t2){
std::cout << "First method"<< std::endl;
t1 = t2;
}
template <class T>
void assign(T& t1, const T& t2) {
std::cout << "Second method"<< std::endl;
t1 = t2;
}
class A
{
public:
A(int a) : _a(a) {};
private:
friend A operator+(const A& l, const A& r);
int _a;
};
A operator+(const A& l, const A& r)
{
return A(l._a + r._a);
}
int main ()
{
A a = 1;
const A b = 2;
assign(a, a);
assign(a, b);
assign(a, a + b);
}
The output is
First method
Second method
Second method
I don't see why. Shouldn't the last call to assign activate the first version, since (a+b) doesn't return a const A object?

An expression doesn't only have a value and a type, but it also has a value category. This category can be
An lvalue: These expressions generally refer to declared objects, references, functions or dereference results of pointers.
An xvalue: These are the result of generating an unnamed rvalue reference. Rvalue references are created by T&& instead of T&. They are a C++11 concept, and you can ignore them here. Mentioned only for sake of completeness.
An prvalue: These are the results of casts to non-reference types (like A(10)) or computing/specifying a value, like 42 or 2 + 3.
An lvalue reference requires an lvalue expression for initialization. That is, the following is invalid:
A &x = A(10);
The reason behind this is that only lvalue expressions refer to things that are suitable and intended for staying alive a longer time than only for the duration of the initialization. Like, a declared object is alive until exiting its block (if it was a local non-static variable) or until the end of the program (if it was declared outside functions and classes). The rvalue expression A(10) refers to an object that dies already when the initialization is finished. And if you said the following, it would not make any sense of all, because pure values like 10 don't have an address at all, but references require some sort of identity to which they bind, which in practice is implemented by taking the address of their target internally in compilers
int &x = 10; // makes totally no sense
But for const references, C++ has a backdoor. When initialized with a prvalue, a const lvalue reference will automatically lengthen the lifetime of the object, if the expression refers to an object. If the expression has a non-object value, C++ creates a temporary object with a value of that expression, and lengthens the lifetime of that temporary, binding the reference to that temporary:
// lifetime of the temporary object is lengthened
A const& x = A(10);
// lifetime of the automatically created temporary object is lengthened
int const& x = 10;
What happens in your case?
Now the compiler in your case, because you supply a temporary object, will choose the version that has a A const& parameter type rather than a A& parameter type.

(a + b) returns a temporary object, though, and can therefore only be bound to a constant reference.

a+b returns a temporary, if you were allowed to catch a non-const reference to it you would be able to change it and then what? The temporary goes out of scope, and the changes done to it can never be captured by the application. In C++03 temporaries will be bound to const references types.
By the way, this has nothing to do with templates. Rewrite your example to use straight 'A's and you will observe the same behavior.

Related

Why does a friend operator require const-ness on its operands while a member operator does not?

This may be related to the fact that taking references to temporary objects is allowed only in some situations, but I'm not sure how this rule applies.
In this example, the compiler stops at line a + b + c because a + b returns an Int, to which a reference must be taken in order to evaluate (a+b) + c. According to what I've read, this is a deliberate choice so temporary values aren't mutated. The way to fix this code is to change the signature to Int operator + (const Int & a, const int & b).
struct Int {
int a;
Int() { a = 0; }
Int(int i) { a = i; }
friend Int operator + (Int &, const Int &);
};
Int operator + (Int & a, const Int & b) {
return Int(a.a + b.a);
}
int main() {
Int a, b, c;
a + b + c;
return 0;
}
However, if we change the operator from being a friend function to being a struct member, then this code, which should have the same problem with creating a temporary mutable value, actually compiles:
struct Int {
int a;
Int() { a = 0; }
Int(int i) { a = i; }
Int operator + (Int &);
};
Int Int::operator + (Int & v)
{
return Int(a + v.a);
}
int main()
{
Int a, b, c;
a + b + c;
return 0;
}
Why exactly does this happen? These two situations seem mostly identical, but in the latter, not only is const-ness not required for the RHS value, it's not required for the LHS value either.
In the first case, the code is effectively equivalent to:
operator+(operator+(a, b), c);
Here, the expression operator+(a, b) is an rvalue, which cannot be bound to a non-const lvalue reference.
In the second case, we have:
a.operator+(b).operator+(c);
There is no such problem, since only lvalues b and c are bound to that non-const lvalue reference parameter.
As #BenVoigt pointed out, you may wonder about calling a non-const member function on a temporary. This is perfectly legal. There is a special rule about it in the Standard, which is described in detail in the #dfrib's answer.
Summary:
Try a+(b+c) and watch your member version fail.
C++ has named values (aka lvalues, named because they are values that make sense on the LEFT hand side of an = assignment) and temporaries (aka rvalues, which go on the RIGHT hand side of an = assignment). (this paragraph contains simplifications).
lvalues will bind to non-const references, while rvalues will only bind to const references. So Int& won't bind to a temporary, while Int const& or const Int& (same thing) will.
The error you are getting is that a+b+c is parsed as (a+b)+c by the order of operations in C++. And the result of a+b is a temporary (an rvalue). So it won't bind to Int&.
A method like Int operator+(Int& rhs) takes *this as the left-hand argument to +, and rhs is the right-hand argument. So (a+b)+c, a+b is *this and c is rhs. Member operator+ works on temporaries, so all is good.
A friend like Int operator+(Int&, Int const&) -- you'll note the Int& is on the left. The left in (a+b)+c is a temporary when you add +c. So it fails, because a=b won't bind to Int&
Finally, there are quirks in how members bind to temporaries. Because members where added to C++ 20+ years before rvalue/lvalue references where, they'll bind to temporaries far more liberally than you might expect (by default).
In more detail:
Your operator+ member takes a non-const reference on the right hand side. Your friend takes a non-const reference on the left hand side.
a+b+c in C++ is parsed as (a+b)+c. In every case, the right hand side of a + has a named value -- an lvalue -- and named mutable values can bind to non-const references.
However, the +c has a temporary on the left hand side -- an rvalue -- and temporaries cannot bind to non-const lvalue references.
The final quirk has to do with the fact that a member like this:
Int Int::operator + (Int & v)
{
return Int(a + v.a);
}
can be called on an rvalue (a temporary), even though it is non-const. l/r value qualification of objects was added after initial member syntax was.
There are a bunch of ways you are permitted to declare "what kind of object, const or non const, temporary or not, will this method work on". Their signatures look like:
Int operator + (Int const& v);
Int operator + (Int const& v) const;
Int operator + (Int const& v) &;
Int operator + (Int const& v) const&;
Int operator + (Int const& v) &&;
Int operator + (Int const& v) const&&;
if you don't include a trailing & or a &&, your methods bind to both rvalues (temporaries) and lvalues (named values with identity). This is because l/r value references where added 20+ years after methods where added to C++, so the default method declaration works with both (for backward compatibility reasons).
friend Int operator + (Int &, const Int &);
corresponds to
Int operator + (const Int &) &;
which, as a method, will fail with basically the same error message as the friend does.
You can also induce the
friend Int operator + (Int &, Int const&);
method to work by typing:
a+(b+c)
as now the left-hand side of +c is a temporary, and refuses to bind to Int&.
This will also make the
Int operator+(Int&)
version fail, as now the right hand side of a+ is (b+c), which is a temporary, and won't bind to Int&.
General Advice:
When working on operators for a class, do this.
Int operator+=(Int const& rhs)&;
this is the increment by operator. It should be a member, and it should have the & qualification at the end -- incrementing a temporary makes no sense.
Then write:
friend Int operator+(Int lhs, Int const& rhs) {
lhs += rhs;
return lhs;
}
here we use the increment operator to implement +. If our class has efficient move constructors (as it should), this will also be very close to optimal.
The left hand side argument we take by value. We then increment it. Then we return it (and this implicitly moves).
When you type:
a+b+c
this becomes
(a+b)+c
we copy a and get:
copy of a += b
we then move the copy of a into the result. This is then elided (zero-cost moved) into the left hand argument of +c.
We then increment that, and move it to the result.
So, eliminating moves (which should be near zero cost even on most complex objects), we get:
Int temporary = a;
temporary += b;
temporary += c;
return temporary;
as the result of a+b+c.
This pattern works with simple things like fake integers, more complex things like strings, and a myriad of other situations. And it gives you += and + for about the same amount of coding work as typically writing + does.
Rvalues cannot bind to non-const lvalue references
friend Int operator + (Int &, const Int &);
In this signature, the left hand side argument is non-const ref, and rvalues cannot bind to non-const lvalue references to extent their lifetime.
a + b + c;
// ^^^^^ - result: rvalue (temporary)
// -> (rvalue) + c
// -> rvalue cannot bind to non-const lvalue reference.
They may, however, bind to const lvalue references to extend their lifetime.
These two situations seem mostly identical, [...]
An representable example for the member function approach would be to CV- and ref-qualify the member function as non-const lvalue ref:
Int operator+(Int &) &; // ref-qualifier: &
// const-qualifier: non (non-const)
in which case you will get a similar error as for the friend function above.
Similarly, the compiling example
friend Int operator + (Int &, const Int &);
would be comparable to const and &-qualifying the member function:
Int operator+(Int &) const &;
as an rvalue *this could bind to const &, extending its lifetime.
Exception for non-static member functions with no ref-qualifiers, even though the implicit object parameter may be of non-const lvalue reference type
The reason why the non-ref-qualified case is accepted may come as a surprise, as the type of the implicit object parameter is an lvalue reference if no ref-qualifier is supplied, as per [over.match.funcs]/4: [emphasis mine]:
For non-static member functions, the type of the implicit object parameter is
“lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
However, as covered by [over.match.funcs]/5, this is an explicit exception for the case where no ref-qualifier has been supplied [emphasis mine]:
[...] For non-static member functions declared without a ref-qualifier, an additional rule applies:
even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.

Universal reference l-value not copying object

Why these asserts work for the below code? The universal reference should bind to l-value reference run(T& a), and copy an object b from a. However both objects addresses "a" and "b" are the same in the run() function. Tested with C++11/14/17/2a gcc-9.2 and clang++-6.0. What part of the standard says this is valid? did not find anything related.
#include <cassert>
#include <utility>
template <typename T>
void run(T&& a)
{
T b {std::forward<T>(a)};
++b;
assert(b == a);
assert(&a == &b);
}
int main()
{
int value {10};
run(value); // asserts work, not expected
// run(std::move(value)); // the asserts don't work as expected
}
However both objects addresses "a" and "b" are the same in the run() function.
When being passed an lvalue, T is deduced as lvalue-reference, i.e. int&. (int& && collapses to int&, so the type of function parameter a is int&.) Then b is declared as a reference binding to a.
When being passed an rvalue, T is deduced as int. (So the type of function parameter a is int&&.) Then b is declared as an independent variable copied from a.
In run(value), value is an lvalue, and it needs to match with T&&. Lvalues cannot bind to rvalue references, so T = int and T = int&& won’t do, as then T&& = int&&. The only thing that works is T = int&. Due to reference collapsing, an rvalue reference to lvalue reference is an lvalue reference, so the instantiation of run looks like:
template<>
void run<int&>(int &a) {
int &b{a}; // expanding std::forward
++b;
assert(b == a);
assert(&b == &a);
}
Obviously, the assertions always pass. Now, for run(std::move(value)), the argument is indeed an rvalue, and you get T = int. Then
template<>
void run<int>(int &&a) {
int b{std::move(a)};
++b;
assert(b == a);
assert(&b == &a);
}
This of course fails. Perhaps you should replace
T b{std::forward<T>(a)};
with
std::decay_t<T> b{std::forward<T>(a)};
This will remove references from T (ensuring b is a new (copied/moved) object) and also handle arrays and functions (by making b a pointer even if a isn’t).
Doubt you need them, but [temp.deduct.call]/3 talks about the template deduction of forwarding references, and [dcl.init.list]/3.9 says that list-initializing a reference just binds it to the element of initializer list. Also [forward], well, explains std::forward<T>. Basically, if T is an lvalue reference, then std::forward<T>(x) is an lvalue, and otherwise an xvalue (a kind of rvalue). (Basically it’s a conditional std::move.)

if I am not allowed to assign rvalues to reference why does the following snippet work, how does this work internally?

I am playing around with templates and c++ syntax and I noticed that you are able to pass by reference simply by calling add(2,3) when the function is defined as below:
#include <iostream>
using namespace std;
auto add(const int& lhs, const int& rhs)
{
return lhs + rhs;
}
int main()
{
cout << add(2, 3) << '\n';
getchar();
return 0;
}
How come doing this is illegal:
int& r = 3;
but I can call add(2,3) without a problem, aren't I doing the same in both cases, assigning a rvalue to a reference?
if I am not allowed to assign rvalues to reference why does the following snippet work
Because you've mistaken. You are allowed to initialise lvalue references to const with rvalues.
how does this work?
A temporary object is created and the reference is bound to that temporary.
Furthermore, the lifetime of the temporary object is normally until the end of the full expression, but when bound to a reference, that lifetime is extended for the lifetime of the reference. In this particular case, the reference argument doesn't have a longer life time than the full expression where the function call is, so this rule is not important.
How come doing this is illegal:
int& r = 3;
Because r is not a reference to const. It is a reference to non-const.
If you did change the constness of the reference, then this would be well-formed, and the rule from paragraph above would become relevant.

Is the lifetime of a reference extended?

I'd like to pass a reference into a function. This code does not work, as I'd expect:
struct A {
};
void foo(A& a) {
// do something with a
}
int main(int, char**) {
foo(A());
}
I get the compile error
invalid initialization of non-const reference of type A& from an rvalue of type A
But when I just add the method A& ref() to A like below and call it before passing it along, it seems I can use a. When debugging, the A object is destroyed after foo() is called:
struct A {
A& ref() {
return *this;
}
};
void foo(A& a) {
// do something with a
}
int main(int, char**) {
foo(A().ref());
}
Is this valid code according to the standard? Does calling ref() magically extend the lifetime of the object until foo() returns?
Your code is perfectly valid.
In this line
foo(A().ref());
The instance of a temporary A lives until the end of the statement (;).
That's why it's safe to pass A& returned from ref() to foo (as long as foo doesn't store it).
ref() by itself does not extend any lifetime, but it helps by returning an lvalue reference.
What happens in the case of foo(A()); ? Here the temporary is passed as an rvalue. And in C++ an rvalue does not bind to non-const lvalue references (even in C++11, an rvalue reference does not bind to non-const lvalue references).
From this Visual C++ blog article about rvalue references:
... C++ doesn't want you to accidentally modify temporaries, but directly
calling a non-const member function on a modifiable rvalue is explicit, so
it's allowed ...
A() creates a temporary object of type A. The object exists until the end of the full expression in which it is created. The problem in your original code is not the lifetime of this temporary; it's that the function takes its argument as a non-const reference, and you're not allowed to pass a temporary object as a non-const reference. The simplest change is for foo to take it's argument by const reference, if that's appropriate to what the function does:
void foo(const A&);
int main() {
foo(A());
}
There are several questions in this question. I'll attempt to address all of them:
First, you cannot pass a temporary (prvalue) of type A to a function taking A& because non-const lvalue references cannot bind to rvalues. That's a language restriction. If you want to be able to pass a temporary, you either need to take a parameter of type A&& or of type A const& - the latter since temporaries can bind to const lvalue references.
Is this valid code according to the standard? Does calling ref() magically extend the lifetime of the object until foo() returns?
There is no lifetime extension going on in your program at all. From [class.temp]:
There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array with
no corresponding initializer (8.6). The second context is when a copy constructor is called to copy an element
of an array while the entire array is copied (5.1.5, 12.8). [...] The third context is when a reference is bound to a temporary.
None of those contexts apply. We are never binding a reference to a temporary in this code. ref() binds *this to an A&, but *this is not a temporary, and then that resulting reference is simply passed into foo().
Consider this variant of your program:
#include <iostream>
struct A {
A& ref() { return *this; }
~A() { std::cout << "~A()\n"; }
};
int main() {
auto& foo = A().ref();
std::cout << "----\n";
}
which prints
~A()
----
illustrating that there is no lifetime extension.
If instead of binding the result of ref() to a reference we instead bound a member:
#include <iostream>
struct A {
A& ref() { return *this; }
int x;
~A() { std::cout << "~A()\n"; }
};
int main() {
auto&& foo = A().x;
std::cout << "----\n";
}
then we actually are binding a temporary to a reference and that third context applies - the lifetime of the complete object of the subobject to which the reference is bound is persisted for the lifetime of the reference. So this code prints:
----
~A()

Are unnamed objects and temporary objects equivalent?

In my effort to understand rvalue references, I have been pondering when the compiler will determine that a particular function argument is an rvalue reference, and when it will determine it to be an lvalue reference.
(This issue is related to reference collapsing; see Concise explanation of reference collapsing rules requested: (1) A& & -> A& , (2) A& && -> A& , (3) A&& & -> A& , and (4) A&& && -> A&&).
In particular, I have been considering if the compiler will always treat unnamed objects as rvalue references and/or if the compiler will always treat temporary objects as rvalue references.
In turn, this leads me to question whether unnamed objects are equivalent to temporary objects.
My question is: Are unnamed objects always temporary; and are temporary objects always unnamed?
In other words: Are unnamed objects and temporary objects equivalent?
I might be wrong, since I'm not sure what the definition of "unnamed object" is. But consider the argument of the foo() function below:
void foo(int)
{ /* ... */ }
int main()
{ foo(5); }
foo()'s argument is unnamed, but it's not a temporary. Therefore, unnamed objects and temporary objects are not equivalent.
Temporary objects can be named.
Very common case - when passed as a parameter to a function.
Another less common case - binding a const reference to an rvalue result of a function.
int f(int i) { return i + 1; }
int g() { const int &j = f(1); return j; }
Unnamed objects are often temporary, but not always. For example - anonymous union object:
struct S
{
union { int x; char y; };
} s;
And, of course, any object created by operator new.
Perhaps there are other cases, but even only these can serve as counterexamples to the hypothesis :)
I have been pondering when the compiler will determine that a particular function argument is an rvalue reference, and when it will determine it to be an lvalue reference.
I assume you are talking about function templates with universal reference parameters, like this?
template<typename T>
void foo(T&& t)
{
}
The rules are very simple. If the argument is an rvalue of type X, then T will be deduced to be X, hence T&& means X&&. If the argument is an lvalue of type X, then T will be deduced to be X&, hence T&& means X& &&, which is collapsed into X&.
If you were really asking about arguments, then the question does not make much sense, because arguments are never lvalue references or rvalue references, because an expression of type X& is immediately converted to an expression of type X, which denotes the referenced object.
But if you actually meant "How does the compiler distinguish lvalue arguments from rvalue arguments?" (note the missing reference), then the answer is simple: the compiler knows the value category of every expression, because the standard specifies for every conceivable expression what its value category is. For example, the call of a function is an expression that can belong to one of three value categories:
X foo(); // the expression foo() is a prvalue
X& bar(); // the expression bar() is an lvalue
X&& baz(); // the expression baz() is an xvalue
(Provided, of course, that X itself is not a reference type.)
If none of this answers your question, please clarify the question. Also, somewhat relevant FAQ answer.