9

I have been attempting to understand when and when not a lambda with a capture-default odr-uses a variable with automatic storage duration defined in its surrounding scope (prompted by this answer). While exploring around this I came across a little curiosity. GCC and Clang appear to disagree about the value category of the id-expression n in the following code:

template <typename T> void assert_is_lvalue(const T&) {} template <typename T> void assert_is_lvalue(const T&&) = delete; int main() { const int n = 0; [=] { assert_is_lvalue(n); }; } 

Clang compiles the code successfully, while GCC does not (error: use of deleted function). Which one is correct? Or is this something that is unspecified or implementation-defined?

Binding a reference to an object is supposed to odr-use it, and this is confirmed by removing the lambda's capture-default and observing that both compilers then complain that n can not be implicitly captured without a capture-default.

Marking the lambda as mutable makes no appreciable difference to the compilers' output.

7
  • GCC does not complain about n not being captured if we remove the capture-default. Seems to just be an obvious bug. Commented Apr 29, 2017 at 20:26
  • @Columbo Well spotted, I missed that. I don't think it's a bug, though. I think gcc is treating n as a constant expression, which it may be allowed to do. Commented Apr 29, 2017 at 21:58
  • 4
    Yes, GCC is known to incorrectly do constant folding in lambda ultra-early. Commented Apr 29, 2017 at 22:41
  • 1
    it looks like a premature optimization made by the compiler trying to make n constexpr, then rvalue gets deduced and fails in your example. Definitelly a GCC bug Commented Feb 8, 2018 at 13:08
  • 4
    Works with gcc-8.1 Commented Jul 25, 2018 at 11:31

1 Answer 1

2

It turns out gcc behavior has changed from gcc-7.5 upward! I used the following code to see how n is captured within lambda and which template is matched.

#include <iostream> #include <string> #include <typeinfo> #include <type_traits> #include <memory> #include <string> #include <cstdlib> template <class T> constexpr std::string_view type_name() { using namespace std; #ifdef __clang__ string_view p = __PRETTY_FUNCTION__; return string_view(p.data() + 34, p.size() - 34 - 1); #elif defined(__GNUC__) string_view p = __PRETTY_FUNCTION__; #if __cplusplus < 201402 return string_view(p.data() + 36, p.size() - 36 - 1); #else return string_view(p.data() + 49, p.find(';', 49) - 49); #endif #elif defined(_MSC_VER) string_view p = __FUNCSIG__; return string_view(p.data() + 84, p.size() - 84 - 7); #endif } template <typename T> void assert_is_lvalue(const T& param) { std::cout << " T is " << type_name<T>() << " param is " << type_name<decltype(param)>() << '\n'; } //template <typename T> void assert_is_lvalue(const T&&) = delete; template <typename T> void assert_is_lvalue(const T&& param) { std::cout << " T is " << type_name<T>() << " param is " << type_name<decltype(param)>() << '\n'; } int main() { const int n = 0; [=] { std::cout << " n is " << type_name<decltype(n)>() << '\n'; assert_is_lvalue(n); }(); return 0; } 

and here are the results:

gcc-7.5

n is const int T is int param is const int&& 

gcc-8.1

n is const int T is int param is const int& 

you can play with the code here.

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.