Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do lambda expressions that appear in different definitions of a same entity produce the same closure type?

In C++20 we can use lambda expressions in unevaluated contexts. Consider the following C++20 code:

foo.cpp

#include <typeinfo>

struct Foo {
  using T = decltype([]{});
};  // struct Foo

const std::type_info& getType() { return typeid(typename Foo::T); }

main.cpp

#include <iostream>
#include <typeinfo>

struct Foo {
  using T = decltype([]{});
};  // struct Foo

const std::type_info& getType();

const std::type_info& getTypeFromMain() { return typeid(typename Foo::T); }

int main() {
  std::cout << std::boolalpha 
            << (getType() == getTypeFromMain());
  return 0;
}

What should the program output under standard C++?

AFAIK, GCC and clang output false. They believe that all lambda expressions should produce distinct closure types and are "local" to translation units.

On the other hand, [basic.def.odr] 13.10 states that closure types introduced by corresponding lambda expressions in different definitions of the same entity within different translation units should be the same, and the program should output true. Are GCC and clang wrong on this?

like image 430
Lancern Avatar asked Jan 28 '26 09:01

Lancern


1 Answers

Yes, the closure type is the same in all translation units, and the compilers are still catching up to that. The mechanism for doing so is generating (mangled) names for the closure type from its placement within the overall definition (by, say, counting them, as mentioned in the comments). The reason in the standard that this works is somewhat subtle.

[basic.def.odr]/13.10 does not specify that the closure types are the same; it uses "shall" and so requires that they be the same (which, given the following, has an effect only in even more subtle cases). It is /14 that has that effect: "the behavior is as if there is a single entity with a single definition", which necessarily produces a single closure type. Even then, it's not proper to say that the lambda expressions "produce the same closure type": rather, only one of the closure types produced has any relevance to the program as a whole.

Note that without provisions like this, simple functions like

#ifndef MY_HEADER_HH
#define MY_HEADER_HH

inline auto make_assign(int i) {return [i](int &x) {x=i;};}

#endif

wouldn't work if, say, multiple translation units tried to add various make_assign(…) objects to a std::vector<decltype(make_assign(0))>.

like image 151
Davis Herring Avatar answered Jan 30 '26 23:01

Davis Herring