#include <iostream>
#include <type_traits>
int main(){
int i = 1;
int& j = i;
auto f2 = [j = j]() {
std::cout
<< std::is_same_v<decltype(j), int&>
<< std::is_same_v<decltype((j)), int&>
<< std::is_same_v<decltype((j)), const int&>;
};
auto f3 = [=]() {
std::cout
<< std::is_same_v<decltype(j), int&>
<< std::is_same_v<decltype((j)), int&>
<< std::is_same_v<decltype((j)), const int&>;
};
f2();
f3();
}
The following output is the result of the c++17 standard
| gcc | clang | msvc |
|---|---|---|
| 001 | 001 | 010 |
| 110 | 101 | 101 |
Or all the compilers are wrong?
Clang is correct; the results given by Clang conform to the wording introduced by P0588R1, which was accepted after the publication of C++17 but has DR status, meaning that it is recommended that implementations treat the original specification prior to P0588R1 as having been incorrectly drafted, and that the rules in P0588R1 should be applied by implementations even in C++17 mode and earlier.
The following example from P0588R1 illustrates the application of the rules to an example very similar to your second example,
f3:In your example, you have the reference
jinstead of the referencer, but otherwise, it's the same. Sodecltype(j)should beint&, whiledecltype((j))should beconst int&.To elaborate, because
decltype(j)anddecltype((j))are not odr-uses ofj, they denote the original entity from the enclosing scope. (There is no copy ofjstored inside the lambda, because the lambda did not odr-usej; but even if the lambda had odr-usedjand thus created a copy of it, non-odr-uses ofjwill not refer to that copy.) However, confusingly, the type ofjinside the lambda is not necessarily the type of the entity thatjdenotes. Instead, to determine the type ofj, we consider a hypothetical odr-use ofj; then, we take the type from the type of a hypothetical member access expression to that hypothetical copy.In the case of
decltype(j), becausejis not parenthesized,decltypeignores the type of the expression, and gives the declared type of the denoted entity; this isint&, the declared type of the variablej. Indecltype((j)), becausejis parenthesized,decltypeignores the declared type ofj, and looks at the type and value category of the expressionj. Now, the hypothetical odr-use ofjwould result in anintmember being declared in the closure type, but a hypothetical member access expression to that member would give an lvalue of typeconst int, because the lambda is not mutable. Consequently,decltype((j))isconst int&.Now, let's apply these rules to the case of
f2. Here's where it gets a bit tricky because you have an init-capture. The init-capture actually introduces a new namejthat shadows thejbelonging tomain. That new name denotes a member of the closure type, whose type is determined as if you had writtenexcept that the first
j's scope doesn't begin until after the closing square bracket of the lambda-introducer, so the secondjrefers to thejthat belongs tomain.Now, any mention of
jin the lambda body, whether an odr-use or not, denotes thejdeclared by the init-capture. Thatjhas declared typeint, sodecltype(j)isint. Fordecltype((j)), we again consider a member access expression that would denote the memberj; that member access expression would again be an lvalue of typeconst int, sodecltype((j))isconst int&.