11

If I understood correctly this answer and referenced standard section [dcl.type.auto.deduct-5], the code:

decltype(auto) a = e;

is always equivalent to

decltype( e  ) a = e;

But now the problem appears if instead of e I put the lambda expression to decltype(auto):

decltype(auto) lambda = [](){};

This compiles, to my surprise, successfully in both gcc and clang. Reason for the shock I've experienced lays in standard which says specifically that lambda should not occur in unevaluated operand [expr.prim.lambda#2] (emphasis mine):

A lambda-expression is a prvalue whose result object is called the closure object. A lambda-expression shall not appear in an unevaluated operand, in a template-argument, in an alias-declaration, in a typedef declaration, or in the declaration of a function or function template outside its function body and default arguments.

But as I mentioned the example would be equivalent to:

decltype([](){}) lambda = [](){};

The above code written explicitly obviously would be ill-formed. Of course we could assume that the statement [](){} inside decltype is kind of reference that isn't really a reference like in case of structured bindings, but maybe there is a special rule in standard that I've missed covering lambda initializing decltype(auto)?

W.F.
  • 13,888
  • 2
  • 34
  • 81

1 Answers1

7

This answer is based on my interpretation of the relevant Standard text. Those sections are not very clear with divided opinions, and thus it is currently hard to know the exact meaning of them. It seems that, excluding a possible oversight, the major compilers seem to agree that the definition in question is indeed well-formed.

In addition, it is my opinion that it would be very surprising to hear that the definition were ill-formed.


Reason for the shock I've experienced lays in standard which says specifically that lambda should not occur in unevaluated operand [...]

Where do you see that a lambda appears in an unevaluated context?

decltype(auto) lambda = [](){};

I don't see it, because there is none. The lambda is used as an initializer, which is completely legal.

Now your confusion probably comes about because you seem to think that the above statement is equivalent to

decltype([](){}) lambda = [](){};

That's not the case though, strictly speaking. If you look at the language of the wording, there is a small difference (highlighted by me):

If the placeholder is the decltype(auto) type-specifierT shall be the placeholder alone. The type deduced for T is determined as described in [dcl.type.simple], as though e had been the operand of the decltype.

The key word here is though. It just means that the deduction happens as if it were decltype(e), meaning that the deduction rules of decltype apply instead of those for auto for the operand e.

Here, the operand e is indeed the lambda, but that is completely legal, because the Standard mandates that the behavior is the same as if you would have written decltype([](){}), meaning that of the rules of decltype deduction apply for the lambda. Now [expr.prim.lambda]/2 doesn't apply here, because the lambda is not in an unevaluated context, so it is actually legal for the compiler to use decltype([](){}) to deduce the type, meaning that the decltype rules have to be used for the lambda.

Sure, if you write decltype([](){}), the program is ill-formed, but that is not the case here, as mentioned above.

In this case, because a lambda expression is a prvalue, the deduced type should just be the type of the lambda.

At least that's how I understand it...

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • Well two compilers teams independently understand it like you so there is a huge chance that you are right. Still if as you said `it is not completely equivalent` shouldn't there be a strict rules on how to interpret it? – W.F. Jul 06 '17 at 11:32
  • I mean - shouldn't it act like in case of initializer list - where `auto x6a = { 1, 2 };` is ok but `decltype(auto) x6d = { 1, 2 };` is not? – W.F. Jul 06 '17 at 11:43
  • 1
    @W.F.: "*shouldn't there be a strict rules on how to interpret it*" There is a strict rule; he just quoted it for you. That's why "two compiler teams" got it right. – Nicol Bolas Jul 06 '17 at 13:07
  • 1
    @W.F. I think the use of the braced initializer list with `decltype(auto)` is explicitly prohibited by [this clause](http://eel.is/c++draft/dcl.spec.auto#dcl.type.auto.deduct-3) – Rakete1111 Jul 06 '17 at 13:31
  • 2
    @Rakete1111 No, there's no `return` statement here. It's prohibited because a braced-init-list is not an expression. – Oktalist Jul 06 '17 at 18:58
  • 1
    I disagree, it clearly says " The type deduced for T is determined as described in [dcl.type.simple], as though e had been the operand of the decltype.", which means deduction appears as though you had written `decltype([](){})`, which is illegal. What is the meaning of an illegal code snippet? In other words, what does "It just means that the rules of decltype are used for deduction of e" mean when `e` is a lambda expression? – Johannes Schaub - litb Jul 08 '17 at 10:24
  • "In this case, because a lambda expression is a prvalue, the deduced type should just be the type of the lambda if I understood the rules correctly.", I don't think you can deduce this, because a lambda expression is not allowed as operand of `decltype`. Therefore the rule doesn't apply to lambda expressions. – Johannes Schaub - litb Jul 08 '17 at 10:26
  • @JohannesSchaub-litb The only thing prohibiting `decltype([](){})` is the clause in `[expr.prim.lambda]/2`, but that clause doesn't come into effect with `decltype(auto) a = [](){};` because the lambda is not in an unevaluated context (among other things), and so `decltype` behaves as if the lambda were a prvalue of the corresponding closure object from the lambda. I'll see if I can fix my wording, which I agree is a bit unclear. – Rakete1111 Jul 08 '17 at 11:05
  • It is in an unevaluated context because the rule says "as though e had been the operand of the decltype", and "e" is a lambda expression. "decltype behaves as if the lambda were a prvalue of the corresponding closure object from the lambda." => where does the spec says that? The spec does not say "as though an invented expression e' with the same type and value category as e had been the operand of the decltype". – Johannes Schaub - litb Jul 08 '17 at 12:08
  • @JohannesSchaub-litb It says it right [there](http://eel.is/c++draft/dcl.type.simple#4.5). Basically my point is this: In `decltype(auto) a = [](){};` there is no lambda in an unevaluated context. The "as though" is the key here. "as though" -> "as if" in this context. It doesn't mean that they are strictly equivalent. There is no rule apart from [expr.prim.lambda]/2 prohibiting lambdas in `decltype` and such. It really means that the deduction rules of `decltype` should be used, which exist for lambdas. It doesn't matter that `decltype(e)` is possibly ill-formed. – Rakete1111 Jul 08 '17 at 12:23
  • I agree with "as though = as if", at least the Standard shows no concept of a difference here. So: "The construct behaves as if it were a construct that is ill-formed" means exactly that: The construct is ill-formed. "There is no rule apart from..." -> A single rule that renders the program ill-formed is enough. You don't need two rule violations. Otherwise you could write "void main() { }" and it would be well-formed. – Johannes Schaub - litb Jul 08 '17 at 12:45
  • "It doesn't matter that decltype(e) is possibly ill-formed.".. it matters because an ill-formed program is not a C++ program, so you can't establish rules about them. – Johannes Schaub - litb Jul 08 '17 at 12:47
  • @Johannes It says "as described in ..." which would mean (at least to me) that only the rules there should apply. Lambdas are prohibited in another clause. The same rules apply when you use `decltype(auto)` as a return type for a function returning a lambda, where [you agree](https://stackoverflow.com/a/40438140/3980929) with me. – Rakete1111 Jul 08 '17 at 13:18
  • @Rakete1111 if only the rules "there" apply - how deep into later pages will the rules go? And since the "General principles" section is before that and consequently won't apply anymore, does it still apply that every well-formed program shall be accepted by an implementation? – Johannes Schaub - litb Jul 08 '17 at 13:39
  • @Johannes I don't understand that argument of yours. I would honestly be very surprised if `decltype(auto) a = lambda;` were ill-formed if `decltype(auto) f() { return [](){}; }` is well-formed. Or if both were ill-formed. – Rakete1111 Jul 08 '17 at 13:59
  • @Rakete1111 Well, I think the standard committee's intention could be to make it well formed but also have objections about the used wording... – W.F. Jul 08 '17 at 14:03
  • @W.F. Yes, it is probably an oversight. They really should improve the wording. – Rakete1111 Jul 08 '17 at 14:43
  • @Rakete1111 i used your argument to draw a devils advocate: You said: *"It says "as described in ..." which would mean (at least to me) that only the rules there should apply."* So I took that position and asked whether the rule "implementations shall not reject well-formed programs" should also be ignored, because the rule is stated elsewhere, instead of in the `decltype` paragraph. If that rule should *not* be ignored, then *I* do not understand your argument. – Johannes Schaub - litb Jul 08 '17 at 16:28
  • @Rakete1111 Jahannes got the point there. Maybe you should consider to change your answer a bit..? – W.F. Jul 08 '17 at 17:45
  • @W.F. I can see you folks' point, but I'm still totally convinced. As I see it, the Standard's wording in this case is a bit unclear, but I'm pretty sure I got it right, considering the fact that gcc and clang (and possibly VS) also let it pass, and that it makes sense that it should be well-formed with lambdas. – Rakete1111 Jul 08 '17 at 19:18
  • @Rakete1111 Sorry I haven't seen you've edited your answer already :) Also you probably got me wrong I'm not saying decltype(auto) should not allow to deduce lambda, just pointing that the current wording of the standard suggests that... Cheers! – W.F. Jul 08 '17 at 19:52
  • @W.F. Well yeah, but only it suggests that. It's still pretty unclear what should happen if `decltype(e)` is ill-formed IMO. :) – Rakete1111 Jul 08 '17 at 21:20
  • I'm quite hesitant to delete a +8/-1 answer that has been accepted by the OP… Obviously quite a few people have found this interesting and helpful. Even if it *is* just your interpretation, that's still a valid answer—and it's still what everyone does for language lawyer questions. If someone else wants to post a better answer, they are always free to do so. The qualifier you've added to the top is sufficient to prevent any possible misunderstandings, so I don't see what the harm is to leaving this in place. – Cody Gray - on strike Sep 01 '17 at 16:48