lambda fct returning reference - c++

I have tried hard to make a lambda function return a value by reference without making a copy of the referenced value.
My code example below illustrates the problem. It compiles and runs ok, but with the "//" commented line instead of the line above, it doesn't.
I have found two workarounds (both illustrated in my example):
wrap the result with std::ref()
return a pointer instead of a reference
But both workarounds are not what I really want, and I do not understand why they are necessary: The expression "makeRefA()" has already the type the lambda function returns (const A&) and thus must neither be copied nor converted. By the way: The copy constructor is really called when I do not explicitly delete it (which in my "real" code is a performance problem).
To me it looks like a compiler bug, but I have tried with several C++11-compilers which all show up the same error. So is there something special concerning the "return" statement in a lambda function?
#include <functional>
#include <iostream>
struct A {
A(int i) : m(i) { }
A(const A&) = delete;
int m;
};
void foo(const A & a) {
std::cout << a.m <<'\n';
}
const A & makeRefA() {
static A a(3);
return a;
}
int main() {
std::function<const A&()> fctRef = [&]
{ return std::ref(makeRefA()); }; //compiles ok
//{ return makeRefA(); }; //error: use of deleted function 'A::A(const A&)'
foo(fctRef());
std::function<const A*()> fctPtr =
[&] { return &makeRefA(); };
foo(*fctPtr());
return 0;
}
output:
3
3

By default, the automatically-deduced type of a lambda is the non-reference version of a type
... the return type is the type of the returned expression (after lvalue-to-rvalue, array-to-pointer, or function-to-pointer implicit conversion); (source)
If you want a return type with a reference, you will have to specify it more explictly. Here are some options:
[&]()
-> decltype( makeRefA() )
{ return makeRefA()); };
or simply be fully explicit about the return type with ->
[&]()
-> const A&
{ return makeRefA(); }
If using C++14, then simply use decltype(auto),
[&]()
-> decltype(auto)
{ return makeRefA(); }
The rules for decltype can be complicated at times. But the fact that makeRefA() is an expression (as opposed to simply naming a variable) means that the type of the expression (const A&) is faithfully returned by decltype( makeRefA() ).

You can specify the return type
#include <functional>
#include <iostream>
struct A {
A(int i) : m(i) { }
A(const A&) = delete;
int m;
};
void foo(const A & a) {
std::cout << a.m <<'\n';
}
const A & makeRefA() {
static A a(3);
return a;
}
int main() {
std::function<const A&()> fctRef = [&]()->const A&
// { return std::ref(makeRefA()); }; //compiles ok
{ return makeRefA(); }; // works
foo(fctRef());
std::function<const A*()> fctPtr =
[&] { return &makeRefA(); };
foo(*fctPtr());
return 0;
}

According to http://en.cppreference.com/w/cpp/language/lambda, these rules apply to lambdas with no trailing return type:
In C++11, lvalue-to-rvalue, array-to-pointer, or function-to-pointer implicit conversion are applied to the type of the returned expression. (Here, the lvalue-to-rvalue conversion is what's hitting you.)
In C++14 and later, the type is deduced as for a function whose return type is declared auto; and that in turn follows the rules for template argument deduction. Then, since auto includes no reference specification, that means that references and cv-qualifiers will be ignored.
The effect is probably desirable in most situations: for example, in this lambda expression
[](const std::vector<int>& v) { return v[0]; }
you probably intend to return an int, even though std::vector<int>::operator[] const returns const int&.
As others have mentioned, you can override this behavior by giving an explicit trailing return type.

Related

Const and reference member function qualifiers

Let's say we have member class with two member functions defined as follows:
class SomeClass
{
private:
int val = {};
public:
const int getVarLRef() & {
return val;
}
const int getVarCLRef() const& {
return val;
}
};
int main()
{
auto var1 = SomeClass().getVarCLRef();
auto var2 = SomeClass().getVarLRef();
return 0;
}
I not quite understand what is the difference between const& and &.
Why it works with getVarCLRef if we specified this function as const&? Shouldn't it be allowed to be invoked only with lvalues?
getVarLRef, on the other hand, works just fine and fails to compile in this case as expected.
I use C++11 and gcc 7.3.0
Const and reference member function qualifiers are to be able to apply those qualifier to "this" as for regular parameter, so mainly, you have something like:
int getVarLRef(SomeClass& self) { return self.val; }
int getVarCLRef(const SomeClass& self) { return self.val; }
And there, I think you know that:
getVarCLRef(SomeClass()); // Valid, temporary can bind to const lvalue reference
getVarLRef(SomeClass()); // INVALID, temporary CANNOT bind to non-const lvalue reference
Shouldn't it be allowed to be invoked only with lvalues?
Because rvalue could be bound to lvalue-reference to const too. Just as the following code works.
const SomeClass& r = SomeClass();
On the other hand, rvalue can't be bound to lvalue-reference to non-const, then the invocation of getVarLRef fails as you expected.

What's the meaning of a prototype ending with a & (ampersand) [duplicate]

This question already has answers here:
What is "rvalue reference for *this"?
(3 answers)
Closed 4 years ago.
By mistake, I had a & at the end of a prototype (see example below). Neither gcc nor Clang complains about it. I noticed that the symbol generated is not exactly the same.
Example:
class A
{
public:
void fn() &
{
return;
}
};
int main()
{
A a;
a.fn();
return 1;
}
Name of the symbol without &: _ZN1A2fnEv and with &: _ZNR1A2fnEv.
What does it mean ? Do I miss something ?
Thanks,
The & at the end of the member function declaration is a ref qualifier. It applies to the object value on which the member function is called, and constrains that value's value category:
Functions without ref qualifiers can be called on any value.
Functions with & qualifier can only be called on lvalues.
Functions with && qualifier can only be called on rvalues.
The ref qualifier affects overload resolution, e.g. an overload on a mismatched instance value is not viable.
The standard library doesn't use ref qualifiers much (I can only think of std::optional), so let's make up our own example:
struct X {
explicit X(int n) : s_(n, 'a') {}
std::string s_;
const char* f1() const { return s_.c_str(); }
const char* f2() const & { return s_.c_str(); }
const char* f3() const && { return s_.c_str(); }
};
Now consider the following calls:
int main() {
X x(10);
x.f1(); // OK
X(20).f1(); // OK
x.f2(); // OK
X(20).f2(); // ill-formed
x.f3(); // ill-formed
X(20).f3(); // OK
}
The example also demonstrates why this feature may be useful: When a member function returns a reference to some internal part of the object itself, then it is important that that internal reference does not outlast the lifetime of the object. If your member function is unqualified, then you can very easily introduce lifetime bugs. For example:
const char* s = std::string("abcde").c_str(); // dangling pointer!
One way to improve such "internal state access" APIs is to create different return value (categories) for different ref-qualified overloads. To get back to std::optional, the engaged-access essentially boils down to this set of overloads:
struct MyOptional {
T value_; // assume engaged!
T& get() & { return value_; }
T&& get() && { return std::move(value_); }
};
That means that MyOptional::get returns an lvalue when invoked on an lvalue optional, and an rvalue (in fact an xvalue) when invoked on an rvalue. This means that, given MyOptional x;, the binding T& r = x.get(); is allowed, but T& r = MyOptional().get(); is not, and similarly T&& r = x.get(); is disallowed, but T&& r = std::move(x).get() is allowed.

lvalue reference became invalid after passing through an identity function

Would someone explain why the reference became invalid after going through an "identity" function, foo1? Isn't an "address" to A is passed into and returned by foo1?
struct A {
A(int x) : x_(x) {}
int x_;
};
int main() {
function<const A&(const A& r)> foo1 = [](const A& r) {
return r;
};
vector<A> vec{1, 2, 3};
cout << foo1(vec[0]).x_ << endl; // RUNTIME ERROR
return 0;
}
How does the problem line differ from:
const A& r = vec[0];
const A& r1 = r;
The problem is your lambda. It doesn't do what you think it does:
function<const A&(const A& r)> foo1 = [](const A& r) {
// ~~~~~~
return r;
};
Note that there's no trailing return type. That means that it's automatically deduced. Deduction never gives you a reference type, so this lambda returns an A, not an A const&. That returned temporary A is then bound to the return A const& of function's operator(). That temporary is not lifetime-extended. But the time we finish calling foo1(), we have a dangling reference to that temporary A. This is undefined behavior, which I guess with your compiler, gave you a helpful runtime error.
To fix this, you need to explicitly specify the return type:
function<const A&(const A& r)> foo1 = [](const A& r) -> A const& {
return r;
};
But even this is dangerous, since you can still pass a temporary A into this function and get a dangling reference out. No real way around that one.
The ease of falling into this trap is also LWG Issue 2813
While your function object returns const A& the lambda you provide it does not. It's return type is deduced from the return statement, which is deduced to be A. Try adding an explicit return type like this instead.
function<const A&(const A& r)> foo1 = [](const A& r) -> const A& {
return r;
};

Overloading Based on L-Value versus R-Value

I found in a C++ book the following:
Although we will not be doing it in this book, you can overload a
function name (or operator) so that it behaves differently when used
as an l-value and when it is used as an r-value. (Recall that an
l-value means it can be used on the left-hand side of an assignment
statement.) For example, if you want a function f to behave
differently depending on whether it is used as an l-value or an
r-value, you can do so as follows:
class SomeClass {
public:
int& f(); // will be used in any l-value invocation const
const int& f( ) const; // used in any r-value invocation ...
};
I tried this and it didn't work:
class Foo {
public:
int& id(int& a);
const int& id(int& a) const;
};
int main() {
int a;
Foo f;
f.id(a) = 2;
a = f.id(a);
cout << f.id(a) << endl;
}
int& Foo :: id(int& a) {
cout << "Bar\n";
return a;
}
const int& Foo :: id(int& a) const {
cout << "No bar !\n";
return a;
}
Have I wrongly understood it ?
Either the book's example is flat-out wrong, or you copied the wrong example from the book.
class SomeClass {
public:
int& f(); // will be used in any l-value invocation const
const int& f( ) const; // used in any r-value invocation ...
};
With this code, when you call s.f() where s is an object of type SomeClass, the first version will be called when s is non-const, and the second version will be called when s is const. Value category has nothing to do with it.
Ref-qualification looks like this:
#include <iostream>
class SomeClass {
public:
int f() & { std::cout << "lvalue\n"; }
int f() && { std::cout << "rvalue\n"; }
};
int main() {
SomeClass s; s.f(); // prints "lvalue"
SomeClass{}.f(); // prints "rvalue"
}
Ofcourse the book is correct. Let me explain the workings of an example of what the author meant :
#include <iostream>
using namespace std;
class CO
{
int _m;
public:
CO(int m) : _m(m) {}
int& m() { return _m; } // used as an l-value
int const& m() const { return _m; } // used as an r-value
};
int main()
{
CO a(1);
cout << a.m() << endl;
a.m() = 2; // here used as an l-value / overload resolution selects the correct one
cout << a.m() << endl;
return 0;
}
Output is
1
2
What you misunderstood is the function signature. You see when you have an argument &arg (as in id(&arg)) you pretty much predefine the l-valuness of it, so returning it through a const or non const member function does not change a thing.
The author refers to a common writting style that allows for 'getters' and 'setters' to be declared with a signature different only in const qualifires yet compile and behave correctly.
Edit
To be more pedantic, the following phrase
Recall that an l-value means it can be used on the left-hand side of an assignment statement.
is not valid anymore. lr valuness applies to expressions, and the shortest way to explain it, is that an expression whose adress we can take, is an l-value; if it's not obtainable it's an r-value.
So the syntax to which the author refers to, enforces the member function to be used correctly (correct compilation / overload resolution) at both sides of the assignment operator. This nowdays is no longer relevant to lr valueness.
A const member function can only be called on a const object. It makes no difference what you do with the return value. In your example, f is non-const, so it always calls the non-const version of f(). Note that you can also overload on r-value references (&&) in C++11.

C++11 trailing return member function using decltype and constness

I am trying to understand the trailing return based new function declaration syntax in C++11, using decltype.
In the following code, I am trying to define a member function returning a const & to allow for read-only access to i
#include <iostream>
#include <type_traits>
struct X {
int &i;
X(int &ii) : i(ii) {}
// auto acc() const -> std::add_const<decltype((i))>::type { return i; } // fails the constness test
auto acc() const -> decltype(i) { return i; } // fails the constness test
// const int &acc() const { return i; } // works as expected
};
void modify_const(const X &v) {
v.acc() = 1;
}
int main() {
int i = 0;
X x(i);
modify_const(x);
std::cout << i << std::endl;
return 0;
}
As mentioned in the comments, only the last commented version of acc() works, whereas using the others, the code just compiles and prints value 1.
Question: How do we have to define the acc() function using the new function declaration syntax based on decltype, such that the compilation here fails due to returning a const &int in modify_const, or in other words, such that acc() has a proper const &int return type.
Remark: using int i; instead of int &i; as the member variable in X produces a compile error, as expected.
Edited to better distinguish between constness of v and X::i, respectively. It is the latter I am trying to impose in acc().
The problem is that decltype((i)) return int& and apply const to that type has no effect. You want something like
template <typename T> struct add_ref_const { typedef T const type; };
template <typename T> struct add_ref_const<T&> { typedef T const& type; };
... and then use
auto acc() const -> typename add_ref_const<decltype((i))>::type { return i; }
That is, the const needs to go between the type T and the &. The solution would have been obvious if you had put the const into the correct location: const should go to the right.
There's nothing illegal about a const member function modifying the target of a pointer to non-const, even if that pointer was gotten from a member variable.
From the compiler's perspective, int& IS the correct return type.
Your "modify_const" function is incorrectly named. i is what gets modified, and is not const.
Simply add an & to the left and skip the trailing return type.
struct X {
int &i;
X(int &ii) : i(ii) {}
auto& acc() const { return i; } // Returns const reference
auto& acc() { return i; } // Returns non-const reference
const auto& acc() const { return i; } // Add const to the left to make it even more readable
};
Note that using this syntax you can declare the member variable after you have declared the function.