Bug 66672 - std::is_same wrong result for captured reference value inside a lambda
Summary: std::is_same wrong result for captured reference value inside a lambda
Status: RESOLVED DUPLICATE of bug 63192
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 5.1.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: c++-lambda
: 99152 (view as bug list)
Depends on:
Blocks: lambdas
  Show dependency treegraph
 
Reported: 2015-06-25 18:47 UTC by Thiago Macieira
Modified: 2022-03-11 00:32 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2021-08-12 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Thiago Macieira 2015-06-25 18:47:18 UTC
Given the following code:

====
#include <iostream>
#include <type_traits>

using namespace std;

int main()
{
  int i, &j = i;
  [=]
  {
    cout << is_same<decltype((j)), int      &>::value
         << is_same<decltype((j)), int const&>::value;
  }();
}
====
(trimmed from http://cppquiz.org/quiz/question/127)

With GCC 5.1, the code will print 10, but it should be 01. Clang 3.6, ICC 16 and MSVC 2015 do compile this as expected. The output 10 would be correct if the extra set of parentheses weren't there.

The explanation given on the quiz website is:

> Since the expression for decltype is a parenthesized lvalue 
> expression, §7.1.6.2¶4 has this to say: "The type denoted by 
> decltype(e) is (...) T&, where T is the type of e;" As the
> expression occurs inside a const member function, the expression is
> const, and decltype((j)) denotes int const&. See also the example in 
> §5.1.2¶18.
[NB: it's paragraph 19 as of N4431]

The example in that paragraph of the standard matches almost exactly the code above.

The output is correct with a functor:

====
struct S
{
  S(int &i) : j(i) {}
  void operator()() const
  {
    cout << is_same<decltype((j)), int      &>::value
         << is_same<decltype((j)), int const&>::value;
  };
  int j;    // captured by value
};
int main()
{
  int i;
  S{i}();
}
====
Comment 1 Henry Cooney 2017-08-24 23:09:08 UTC
I tested this behavior as of g++ 7.2.0; is_same<decltype((j)), ...> still seems to indicate that j is an int& inside the lambda. 

I think this behavior is probably incorrect WRT the language spec. For example, the following code produces a compile-time error ('assignment of read-only variable 'j''):

====
int main() {
  int i = 100;
  int &j = i;
  cout << j;
  [=]() 
  {
    cout << is_same<decltype((j)), int      &>::value
         << is_same<decltype((j)), int const&>::value;
    cout << endl;
    j = j + 1;
  }();

  cout << j;
}
====

That suggests that the correct type of j is indeed int const&, not int&!
Comment 2 Lukas 2017-11-26 12:11:14 UTC
I think gcc's result of 10 is correct, and i believe every other compiler is wrong.

Consider the following passages:

http://eel.is/c++draft/expr.prim.lambda#capture-11.sentence-3
    -- "An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type."
http://eel.is/c++draft/basic.def.odr#def:potentially_evaluated
    -- "An expression is potentially evaluated unless it is an unevaluated operand or a subexpression thereof."
http://eel.is/c++draft/basic.def.odr#def:odr-used
    -- "A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless [...]"
http://eel.is/c++draft/dcl.type.simple#4.sentence-3
    -- "The operand of the decltype specifier is an unevaluated operand."
 
Combining (1) and (3) means that variable names refer to the original entity, not the member of the closure, unless that variable name appears in a potentially evaluated expression.
(2) and (4) combined say that the operand of decltype is not a potentially evaluated expression.

Thus, variable names inside a decltype expression always refer to the outside entities.
Comment 3 Anders Schau Knatten 2018-03-15 10:56:24 UTC
Hi, Anders from cppquiz.org here, in which the example code for this bug report originated.

The entire §5.1.2¶18 from C++11 seems to be gone from http://eel.is/c++draft/expr.prim.lambda. It used to say:

"Every occurrence of decltype((x)) where x is a possibly parenthesized id-expression that names an entity of automatic storage duration is treated as if x were transformed into an access to a corresponding data member of the closure type that would have been declared if x were an odr-use of the denoted entity."

So I believe clang is correct according to c++11, and gcc is correct according to whichever standard removed that sentence.

A bug was also opened against cppquiz.org for this very question, you can see the discussion at https://github.com/knatten/cppquiz/issues/96 if you're interested.
Comment 4 Anders Schau Knatten 2018-03-15 20:23:18 UTC
Update: There's a related issue with Clang, in which Richard Smith makes a good argument: https://bugs.llvm.org/show_bug.cgi?id=35423#c2

He refers to http://eel.is/c++draft/expr.prim.id.unqual#2.sentence-2, of which I was previously unaware, which seems to cover what was previously covered by §5.1.2¶18 in C++11. It says:

"If the entity is a local entity and naming it from outside of an unevaluated operand within the declarative region where the unqualified-id appears would result in some intervening lambda-expression capturing it by copy ([expr.prim.lambda.capture]), the type of the expression is the type of a class member access expression ([expr.ref]) naming the non-static data member that would be declared for such a capture in the closure object of the innermost such intervening lambda-expression."

If I read that passage correctly, it says that "even if decltype doesn't odr-use the identifier, and thus it isn't actually captured, the type of the expression is still as if it had been captured."

So I now believe my previous comment was wrong, and that gcc is wrong both according to C++11 and the current draft.
Comment 5 Andrew Pinski 2021-08-12 21:44:21 UTC
*** Bug 99152 has been marked as a duplicate of this bug. ***
Comment 6 Andrew Pinski 2021-08-12 21:46:33 UTC
Confirmed.
A simplier compile time testcase from PR 99152:

void f() {
  float x, &r = x;
  auto l = [=] () {
    static_assert(std::is_same_v<decltype(x), float>);
    static_assert(std::is_same_v<decltype((x)), const float&>);
    static_assert(std::is_same_v<decltype(r), float&>);

    // fails, decltype((r)) is float&
    static_assert(std::is_same_v<decltype((r)), const float&>);
  };
}
Comment 7 Andrew Pinski 2021-08-12 22:57:53 UTC
Dup of bug 63192.

*** This bug has been marked as a duplicate of bug 63192 ***