Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lambda fct returning reference

Tags:

c++

c++11

lambda

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
like image 650
Stefan Avatar asked May 22 '16 14:05

Stefan


3 Answers

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() ).

like image 172
Aaron McDaid Avatar answered Oct 22 '22 18:10

Aaron McDaid


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;
}
like image 20
Shangtong Zhang Avatar answered Oct 22 '22 19:10

Shangtong Zhang


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.

like image 21
Daniel Schepler Avatar answered Oct 22 '22 18:10

Daniel Schepler