When How to Omit the Return Type in a C++11 Lambda

When can we omit the return type in a C++11 lambda?

Your code is being accepted without any warnings because the original C++11 restriction is considered a defect in the standard, which allows implementations to fix the behavior. See CWG DR975, DR1048 and N3638.

975. Restrictions on return type deduction for lambdas

[Moved to DR status at the April, 2013 meeting as part of paper N3638.]

There does not appear to be any technical difficulty that would require the current restriction that the return type of a lambda can be deduced only if the body of the lambda consists of a single return statement. In particular, multiple return statements could be permitted if they all return the same type.

1048. auto deduction and lambda return type deduction.

...

Notes from the November, 2014 meeting:

CWG agreed that the change embodied in paper N3638 should be considered to have been a DR against C++11.

In summary, DR975 proposed modifying the rules for return type deduction for lambda expressions to allow multiple return statements.

DR1048 identifies a discrepancy where the rules for deducing the return type for normal functions using the placeholder type auto differs slightly from the rules proposed in DR975. Specifically, return type deduction for normal functions would discard top-level cv-qualifiers in all cases, where as those for lambda expressions would preserve cv-qualifiers for class types.

N3638 resolves this issue, amongst others.


I doubt there's any way to revert to the original behavior short of finding a compiler version that shipped with C++11 lambda support prior to the implementation of the DR above.

C++11 restrictions on lambda return type

That is slightly imprecise. [expr.prim.lambda]/4:

If a lambda-expression does not include a lambda-declarator, it is
as if the lambda-declarator were (). If a lambda-expression does
not include a trailing-return-type, it is as if the
trailing-return-type denotes the following type:

  • if the compound-statement is of the form

    { attribute-specifier-seqoptreturn expression ; }

    the type of the returned expression after lvalue-to-rvalue
    conversion (4.1), array-to-pointer conversion (4.2), and
    function-to-pointer conversion (4.3);

  • otherwise, void.

So the return type is only deduced if the whole body of the lambda expression only consists of one sole return statement.

Both GCC and Clang are not standard conforming in this case as they issue an error message if and only if two return statements lead to inconsistent deductions. This is because they already implemented the C++14 standard which deducts the return type even with multiple return statements and/or multiple other statements present. [expr.prim.lambda]/4 specifies that

The lambda return type is auto, which is replaced by the
trailing-return-type if provided and/or deduced from return statements as described in 7.1.6.4.

§7.1.6.4/9

If a function with a declared return type that contains a placeholder
type has multiple return statements, the return type is deduced for
each return statement. If the type deduced is not the same in each
deduction, the program is ill-formed.

Return type of a C++ lambda

Since the return type can depend on the arguments given to the functor, you need to specify them somewhere in order to query the return type. Therefore, when speaking of generic functors (not restricting them to (non-generic) lambdas), it's not possible to determine the return type when not knowing the types of the arguments.

C++11 has the keyword decltype which can be used in conjunction with a trailing return type in order to specify the return type of your function by naming an expression which can depend on the function arguments (here, it depends on what Func is):

template<typename TFunctor>
auto MyFunc(TFunctor &Func) -> decltype(Func(/* some arguments */))
{ ... }

So if you were to call it for example with no argument (I assume this when looking at your lambda example), simply write:

template<typename TFunctor>
auto MyFunc(TFunctor &Func) -> decltype(Func())
{
return Func();
}

In C++14, you can even completely omit the return type and simply write

template<typename TFunctor>
auto MyFunc(TFunctor &Func)
{
return Func();
}

Note that even in C++03, you don't have to provide another function argument; another template argument is enough:

template<typename TReturn, typename TFunctor>
TReturn MyFunc(TFunctor &Func)
{
return Func();
}

int n = MyFunc<int>(someFunctorReturningAnInt);

Explicit Return Type of Lambda

You can explicitly specify the return type of a lambda by using -> Type after the arguments list:

[]() -> Type { }

However, if a lambda has one statement and that statement is a return statement (and it returns an expression), the compiler can deduce the return type from the type of that one returned expression. You have multiple statements in your lambda, so it doesn't deduce the type.

Can I force a C++11 lambda to return by reference?

You should specify the lambda return type to be int&. If you leave the return type off [and the lambda is of form return expression; it will automatically deduce the return type.

#include <iostream>

class Item
{
public:
int& f(){return data_;}
private:
int data_ = 0;
};

int main()
{
Item item;
auto lambda = [](Item& item) ->int& {return item.f();}; // Specify lambda return type
lambda(item) = 42;
std::cout << item.f() << std::endl;
return 0;
}

Why do lambda functions drop deduced return type reference by default?

I think the place you are stumbling is actually with the expression c.getObj() in the line return c.getObj();.

You think the expression c.getObj() has type const Int&. However that is not true; expressions never have reference type. As noted by Kerrek SB in comments, we sometimes talk about expressions as if they had reference type, as a shortcut to save on verbosity, but that leads to misconceptions so I think it is important to understand what is really going on.

The use of a reference type in a declaration (including as a return type as in getObj's declaration) affects how the thing being declared is initialized, but once it is initialized, there is no longer any evidence that it was originally a reference.

Here is a simpler example:

int a; int &b = a;  // 1

versus

int b; int &a = b;  // 2

These two codes are exactly identical (except for the result of decltype(a) or decltype(b) which is a bit of a hack to the system). In both cases the expressions a and b both have type int and value category "lvalue" and denote the same object. It's not the case that a is the "real object" and b is some sort of disguised pointer to a. They are both on equal footing. It's one object with two names.

Going back to your code now: the expression c.getObj() has exactly the same behaviour as c.m_obj, apart from access rights. The type is Int and the value category is "lvalue". The & in the return type of getObj() only dictates that this is an lvalue and it will also designate an object that already existed (approximately speaking).

So the deduced return type from return c.getObj(); is the same as it would be for return c.m_obj; , which -- to be compatible with template type deduction, as mentioned elsewhere -- is not a reference type.

NB. If you understood this post you will also understand why I don't like the pedagogy of "references" being taught as "disguised pointers that auto dereference", which is somewhere between wrong and dangerous.

What is a lambda expression in C++11?

The problem

C++ includes useful generic functions like std::for_each and std::transform, which can be very handy. Unfortunately they can also be quite cumbersome to use, particularly if the functor you would like to apply is unique to the particular function.

#include <algorithm>
#include <vector>

namespace {
struct f {
void operator()(int) {
// do something
}
};
}

void func(std::vector<int>& v) {
f f;
std::for_each(v.begin(), v.end(), f);
}

If you only use f once and in that specific place it seems overkill to be writing a whole class just to do something trivial and one off.

In C++03 you might be tempted to write something like the following, to keep the functor local:

void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}

however this is not allowed, f cannot be passed to a template function in C++03.

The new solution

C++11 introduces lambdas allow you to write an inline, anonymous functor to replace the struct f. For small simple examples this can be cleaner to read (it keeps everything in one place) and potentially simpler to maintain, for example in the simplest form:

void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Lambda functions are just syntactic sugar for anonymous functors.

Return types

In simple cases the return type of the lambda is deduced for you, e.g.:

void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}

however when you start to write more complex lambdas you will quickly encounter cases where the return type cannot be deduced by the compiler, e.g.:

void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}

To resolve this you are allowed to explicitly specify a return type for a lambda function, using -> T:

void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) -> double {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}

"Capturing" variables

So far we've not used anything other than what was passed to the lambda within it, but we can also use other variables, within the lambda. If you want to access other variables you can use the capture clause (the [] of the expression), which has so far been unused in these examples, e.g.:

void func5(std::vector<double>& v, const double& epsilon) {
std::transform(v.begin(), v.end(), v.begin(),
[epsilon](double d) -> double {
if (d < epsilon) {
return 0;
} else {
return d;
}
});
}

You can capture by both reference and value, which you can specify using & and = respectively:

  • [&epsilon, zeta] captures epsilon by reference and zeta by value
  • [&] captures all variables used in the lambda by reference
  • [=] captures all variables used in the lambda by value
  • [&, epsilon] captures all variables used in the lambda by reference but captures epsilon by value
  • [=, &epsilon] captures all variables used in the lambda by value but captures epsilon by reference

The generated operator() is const by default, with the implication that captures will be const when you access them by default. This has the effect that each call with the same input would produce the same result, however you can mark the lambda as mutable to request that the operator() that is produced is not const.

auto&& return type from a C++ lambda

but I expected -> decltype(auto) to return int&

This is expected behavior of decltype,

(emphasis mine)

Inspects the declared type of an entity or the type and value category of an expression.

1) If the argument is an unparenthesized id-expression or an unparenthesized class member access expression, then decltype yields the type of the entity named by this expression.

So the result of decltype(auto) on std::forward<decltype(foo)>(foo).x_ yields the type of the data member x_, i.e. int.

If you add parentheses as

[](auto&& foo) -> decltype(auto) { return (std::forward<decltype(foo)>(foo).x_); };
// ^ ^

Then

2) If the argument is any other expression of type T, and

a) if the value category of expression is xvalue, then decltype yields T&&;

b) if the value category of expression is lvalue, then decltype yields T&;

c) if the value category of expression is prvalue, then decltype yields T.

Note that if the name of an object is parenthesized, it is treated as an ordinary lvalue expression, thus decltype(x) and decltype((x)) are often different types.

Then, as you said, when pass an lvalue to the lambda the expression (std::forward<decltype(foo)>(foo).x_) is an lvalue, then the return type would be int&; when pass an rvalue the expression is an xvalue, then return type would be int&& (which might cause dangled reference trouble).

For the 2nd case, according to the normal rule of template argument deduction, whenever passed lvalue or rvalue the return type is always int.

The 3rd case is the same as the 2nd one.

For the 4th case, the special rule for forwarding reference is applied, then the return type would be int& when the return expression is lvalue, and int&& when the return expression is rvalue.

C++11 lambda returning lambda

Your code has a bug in that it contains a dangling reference; the c reference will refer to the local variable in the outer lambda, which will be destroyed when the outer lambda returns.

You should write it using a mutable by-value lambda capture:

auto a = []() {
int c = 0;
return [=]() mutable {
cout << c++;
};
};

This relies on a post-standard extension to allow multiple statements in a return-type-deducing lambda; Is there a reason on not allowing lambdas to deduce the return type if it contains more than one statement? The easiest way to fix it is to supply a parameter so that the lambda contains only a single statement:

auto a = [](int c) {
return [=]() mutable {
cout << c++;
};
};

Unfortunately default parameters aren't allowed in lambdas, so you'd have to call this as a(0). Alternatively at the cost of readability you could use a nested lambda call:

auto a = []() {
return ([](int c) {
return [=]() mutable {
cout << c++;
};
})(0);
};

The way this works is that when a executes the inner lambda copies all the referenced variables into an instance of its closure type, which here would be something like:

struct inner_lambda {
int c;
void operator()() { cout << c++; }
};

The instance of the closure type is then returned by the outer lambda, and can be invoked and will modify its copy of c when called.

Overall, your (fixed) code is translated to:

struct outer_lambda {
// no closure
struct inner_lambda {
int c; // by-value capture
// non-const because "mutable"
void operator()() { cout << c++; }
}
// const because non-"mutable"
inner_lambda operator()(int c) const {
return inner_lambda{c};
}
};

If you left c as a by-reference capture, this would be:

struct outer_lambda {
// no closure
struct inner_lambda {
int &c; // by-reference capture
void operator()() const { cout << c++; } // const, but can modify c
}
inner_lambda operator()(int c) const {
return inner_lambda{c};
}
};

Here inner_lambda::c is a dangling reference to the local parameter variable c.



Related Topics



Leave a reply



Submit