3

What is the compiler allowed to omit from by-value default captures, when only some data members of an implicitly captured object are actually used by the functor? E.g.,

struct A {
  // some members we care about:
  char x;
  int y;
  // some huge amount of state we do not:
  std::array<bool, 200000> z;

  int foo() const { return y + 1 }
};

void bar() {
  A a;
  // must the entirety of a be copy captured, or is the compiler allowed to pick/prune?
  auto l1 = [=](){ std::cout << a.x << ", " << a.y << std::endl; };
  // ...
}

Similarly, when if ever is early evaluation allowed to omit broader captures?

void baz(int i) {
  A a2;
  a2.y = i;

  // capture fundamentally only needs 1 int, not all of an A instance.
  auto l2 = [=](){ std::cout << a.foo() << std::endl; }
}

There are at least some situations where making a partial vs. complete copy capture of an element should have no visible external effects beyond lambda size, but I do not know where in the spec to look for the answer to what optimizations are allowable.

4
  • 3
    Note that the as-if rule always applies: even if all of a is captured, the compiler only has to ensure that the output of the program matches the theoretical output, so it could decide to not copy a.z . Commented Jul 25, 2018 at 2:21
  • I am particularly fuzzy on how the as-if rule operates in the face of copying lambdas though, since captured elements can have visible ctor/dtor side-effects, etc. I half expected most optimizations here to be allowed, but the set of interactions that would prohibit the partial copies I suspect to be fairly large and subtle. Commented Jul 25, 2018 at 2:30
  • 1
    Well, if the capturing element will have visible side-effects, then those side effects must occur as a result of the capture. If your A had a non-default copy constructor, that copy constructor must be invoked. If the compiler can prove that after executing the constructor it is not necessary to trivially-copy some other members of the captured class, then the compiler is not required to go through the motions of actually doing so. But it still must invoke the copy-constructor "as if" the entire instance was captured by value. Commented Jul 25, 2018 at 2:45
  • std::array<bool,N> doesn't have any visible side-effects Commented Jul 25, 2018 at 3:54

1 Answer 1

3

I think that, in principle, a compiler would be allowed to optimize this in a way that would capture only a copy of the used member under the as-if rule. The relevant part of [expr.prim.lambda] §2:

[…] An implementation may define the closure type differently from what is described below provided this does not alter the observable behavior of the program other than by changing:

  • the size and/or alignment of the closure type,
  • whether the closure type is trivially copyable, or
  • whether the closure type is a standard-layout class.

However, in a quick test checking for sizeof() of the closure type, none of the major compilers (clang, gcc, msvc) seemed to optimize the closure type itself in such ways.

It should be noted, though, that this only really becomes an issue as soon as you actually store the object obtained from a lambda expression somewhere (e.g. in an std::function). More often than not, the result of a lambda expression will simply be used as an argument to some function template and then thrown away. In such a case, where everything ends up being inlined, the optimizer should (and did in my tests) just throw away code generated for copying around data that is never referenced…

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.