Why Do Lambda Functions Drop Deduced Return Type Reference by Default

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.

A lambda's return type can be deduced by the return value, so why can't a function's?

C++14 has this feature. You can test it with new versions of GCC or clang by setting the -std=c++1y flag.

Live example

In addition to that, in C++14 you can also use decltype(auto) (which mirrors decltype(auto) as that of variables) for your function to deduce its return value using decltype semantics.

An example would be that for forwarding functions, for which decltype(auto) is particularly useful:

template<typename function_type, typename... arg_types>
decltype(auto) do_nothing_but_forward(function_type func, arg_types&&... args) {
return func(std::forward<arg_types>(args)...);
}

With the use of decltype(auto), you mimic the actual return type of func when called with the specified arguments. There's no more duplication of code in the trailing return type which is very frustrating and error-prone in C++11.

I'm Returning a Reference From a Lambda, why is a Copy Happening?

The return type of a lambda is auto ([expr.prim.lambda]/4), so a copy will be made unless you explicitly specify it with the trailing return type:

[](const A* pa) -> const auto& { return *pa; }(pa);

Why do I get a type deduction error for a lambda returning lambda with multiple return paths?

Now obviously those two types are the same,

No, they're not. The type of every lambda expression is a unique, distinct type.

From [expr.prim.lambda]/3:

The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type — whose properties are described below.

Therefore, return type deduction for f fails and does not result in std::function<int()>. The latter is an unrelated library type that isn't somehow magically the "common type" of any closure type.

Of course each of the unique closure types can be converted to std::function<int()>, so if you provide the return type, everything works:

auto f = []() -> std::function<int()> {
return 1 ? []() { return 1; }
: []() { return 2; };
};

Or, as a plain function:

std::function<int()> f() {
return 1 ? []() { return 1; }
: []() { return 2; };
}

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.

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.

Compiler warning: lambda return type cannot be deduced

As @ildjarn says in his comment, your code is simply ill-formed according to the standard.

§5.1.2 [expr.prim.lambda] p4

[...] 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-seqopt return 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.

[...]

That's it, basically if the code inside the curly brackets (called compund-statement in the standard) is anything but return some_expr;, the standard says the return type is undeducible and you get a void return type.

Why can std::function be constructed with a lambda with a different return type?

You can construct a std::function with any object which is callable with the relevant arguments and whose return value is implicitly convertible to the std::function return. int is implicitly convertible to const int&, so the rules are met.

A compiler could feel free to warn about this, but it seems like a lot of work for a particularly corner-y corner case.



Related Topics



Leave a reply



Submit