[cfe-dev] [EXTERNAL] Issue where Lambda capture of reference to globals doesn't actually capture anything

Richard Smith via cfe-dev cfe-dev at lists.llvm.org
Tue Oct 27 12:59:13 PDT 2020


On Tue, 27 Oct 2020 at 12:11, Arthur O'Dwyer <arthur.j.odwyer at gmail.com>
wrote:

> On Mon, Oct 26, 2020 at 8:14 PM Richard Smith via cfe-dev <
> cfe-dev at lists.llvm.org> wrote:
> > On Mon, 26 Oct 2020 at 16:57, Lewis, Cannada via cfe-dev <
> cfe-dev at lists.llvm.org> wrote:
> >>
> >> I’m not a standards reading expert but does
> >>
> >> Note 7:
> >> An id-expression that is not an odr-use refers to the original entity,
> never to a member of the closure type.
> >> However, such an id-expression can still cause the implicit capture of
> the entity.
> >> — end note
> >>
> >> From the text imply that the program https://godbolt.org/z/feKxdK is
> actually implementation defined? Or does gcc have a bug here?
> >
> > GCC has a bug, according to the standard wording. The mention of myfoo
> does not constitute an odr-use, so is not rewritten to use the capture.
> Clang's behavior is correct per the standard wording.
> >
> > The standard rule is certainly surprising in this particular case. I
> think the rule in question is driven by the desire for adding a
> capture-default to a valid lambda to not change its meaning. For example:
> https://godbolt.org/z/nrWsvj
>
> Hmm. It's insane that you can use local variable `x` inside a lambda that
> doesn't capture anything; I wasn't aware of that.
>

Aside: "insane" (here) and "crazy" (below) are somewhat more charged
language than we'd prefer here.


> If the formal wording really says you can do that, then that seems like a
> formal-wording bug to me.
>

It's entirely intentional, though the case that was being thought about at
the time was primarily local constant non-references, not local references
(though the same rules apply to both).

void f() {
  constexpr int k = 5;
  [] { int arr[k]; };
}

... should obviously be valid. And more broadly, the rule is that anything
that you can name without an odr-use is valid. And this isn't at all
special to lambdas; the same thing happens with all nested constructs:

int n;
void g() {
  const int q = 6;
  int &r = n;
  constexpr int *p = &n;
  struct A {
    void f() {
      int arr[q]; // ok
      r = *p + 1; // ok
    }
  };
}

https://godbolt.org/z/avG7qx

GCC and Clang disagree in a different way about whether it's okay to
> dereference a *pointer* inside a lambda without capturing it; here it's
> GCC that is doing the crazy thing, and Clang that is reasonably refusing to
> compile a reference to `x` when `x` hasn't been captured.
> https://godbolt.org/z/bjbr3f
>

Yeah, GCC's behavior is not in line with the language rules in the pointer
case. I'd guess they're using a more-liberal evaluation mode when
evaluating the initializer of the reference, rather than using the strict
constant initializer rules, and that more-liberal mode permits them to read
the initializer values of all const-qualified variables, not only the ones
that are usable in constant expressions. (Clang has a more-liberal mode
too, and I'd expect we also have bugs where a conforming program can tell
the difference.)

The rules in question are approximately equivalent to: if a variable of
const integral or enumeration type, or of reference type, can be constexpr,
then it is implicitly constexpr. And constexpr variables can be used from
anywhere, without triggering an odr-use, if only their value, not their
address, is used. (We don't actually say such things are implicitly
constexpr, and there are corner cases where the outcome is different -- in
particular, for static data members, where constexpr implies inline but
const does not. But "implicitly constexpr" is how they behave.)

If you change your example to declare the pointer to be constexpr then the
two compilers agree again.

The rules about when compilers are able to "constant-fold" away variables
> that otherwise would need to be captured, strikes me as similar in spirit
> to the rules about when compilers are able to "constant-fold" away
> complicated expressions in constant expressions. Some compilers seem to be
> trying to be "helpful" by letting the optimizer's knowledge leak into the
> front-end, and some are following the same rules as one's "head compiler"
> would.
>

I think this is at least not the proximal cause in the standard-conforming
case -- correctly implementing the odr-use rules in the Clang frontend was
a non-trivial effort and wasn't related to constant folding / optimizer
knowledge leaking through. But I do think the standard rules here probably
originally came from looking at what implementations at the time happened
to do and writing that down (eg, use of a const static data member with no
definition is OK, because implementations happen to not emit a reference to
the symbol), and complexity has built up from that point. I'm not sure
where the references-are-implicitly-constexpr part came from, but the
const-ints-are-implicitly-constexpr part is a backwards-compatibility
concern inherited from C++98.

>From a language design perspective, I think it would have made a lot more
sense if we'd said that constexpr variables are implicitly static (so the
question of capture or of use from other scopes can be answered trivially),
and deprecated the "implicit constexpr" behavior for const
integral/enumeration variables and references. Where we've ended up isn't a
great compromise. But this is really the wrong place to have such
discussions :)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20201027/7b3b7d4f/attachment.html>


More information about the cfe-dev mailing list