cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[dcl.fct.default] Does evaluation of lambda used as default argument means to create different closure types each time the function is called without argument #565

Closed ranaanoop closed 2 days ago

ranaanoop commented 4 days ago

Full name of submitter: Anoop Rana

Reference (section label): [dcl.fct.default]

Link to reflector thread (if any): https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115722

Issue description:

The following program produced the output 12 in all three major compilers but current wording seems to make the output 11. Demo

#include <iostream>

int foo(int x = [](){ static int x = 0; return ++x; }()) { return x; };

int main() {
    std::cout << foo() << foo(); // prints "12", not "11"
}

Currently, dcl.fct.default says:

A default argument is evaluated each time the function is called with no argument for the corresponding parameter. A parameter shall not appear as a potentially-evaluated expression in a default argument

And expr.prim.lambda.closure says:

The type of a 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.

From the above quoted references it seems that the output should be 11. But all three compilers produce 12. So it should be made clear if the lambda type in a default argument gets defined once even if it is evaluated twice.

Suggested Resolution:

Maybe something like following in [expr.prim.lambda.closure] would be more clear:

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

Note the added "while being defined(not during evaluation)" in the above modified reference.

jensmaurer commented 2 days ago

Evaluation never creates new types (absent reflection), so why should this differ here?

Your question becomes a bit more interesting for instantiations of a template, but that's not what this issue talks about.

ranaanoop commented 2 days ago

@jensmaurer But if this is used in a multiple header-source project(like we usually declare the function in header and then include that header in multiple source files giving us multiple TUs), then there will be unique types for each TUs and the program will have undefined behavior no diagnostic required, right?

So is the standard clear that this doesn't happen in single TU. That is, in single TU the program is well-formed but in multiple TUs including and using the said function it will have undefined behavior no diagnostic required.

AlanVoore commented 1 day ago

@jensmaurer I think basic.def.odr makes the shown program well-formed because it is in a single TU. It says:

[Note 4: The entity is still declared in multiple translation units, and [basic.link] still applies to these declarations. In particular, lambda-expressions ([expr.prim.lambda]) appearing in the type of D can result in the different declarations having distinct types, and lambda-expressions appearing in a default argument of D might still denote different types in different translation units. — end note]

[Example 6:

inline void f(bool cond, void (*p)()) {
 if (cond) f(false, []{});
}
inline void g(bool cond, void (*p)() = []{}) {
 if (cond) g(false);
}
struct X {
 void h(bool cond, void (*p)() = []{}) {
   if (cond) h(false);
 }
};

If the definition of g appears in multiple translation units, the program is ill-formed (no diagnostic required) because each such definition uses a default argument that refers to a distinct lambda-expression closure type. The definition of X can appear in multiple translation units of a valid program; the lambda-expressions defined within the default argument of X​::​h within the definition of X denote the same closure type in each translation unit. — end example]


Let me know if I'm wrong.

t3nsor commented 1 day ago

As Jens often says, this issue tracker is not a tutorial forum for the standard.

AlanVoore commented 1 day ago

As Jens often says, this issue tracker is not a tutorial forum for the standard.

You're no one to discourage people from having a discussion. People can and should openly talk/ask about what they think is right or wrong. Just because you don't like the question asked or you think it is too primitive/basic to be asked here doesn't mean everyone else thinks the same. More importantly, anyone can and should expression their view/doubts and asks question(if any) without thinking of things like this is not a forum etc. I clearly gave the references from the standard which I think explains the behavior. And then I asked if I was right or not. People are allowed to answer it.

The issue is already closed, so what is the problem in providing a reference and simply asking. I accept that there is also the std-discussion mailing list for this but I came across this issue and just asked about it in the current issue instead of creating a new thread in the std-discussion. I also like the formatting option that github provide as opposed to gmail.

t3nsor commented 1 day ago

This issue tracker exists for a very narrow topic. You can report core issues here and Jens, as chair of CWG, can then schedule them for discussion by CWG. Jens, who has many responsibilities within the committee and is a busy man, has to read all the posts here to make sure he doesn't miss any core issues or any details relevant to core issues. When people post off-topic, they generate email that he then has to read despite it being off-topic.

If you feel that your question is on-topic, please rephrase it as a potential core issue. Would you like wording to be added to the example in the standard to clarify that the program is well-formed if it has only a single translation unit?

For questions about the meaning of the standard, there are many other places to ask, where everyone can participate as much or as little as they please.