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.

Oktalist
  • 14,336
  • 3
  • 43
  • 63
  • GCC does [not complain](http://coliru.stacked-crooked.com/a/87f9b0dbe8e6a118) about `n` not being captured if we remove the capture-default. Seems to just be an obvious bug. – Columbo Apr 29 '17 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](http://stackoverflow.com/q/43467095/1639256). – Oktalist Apr 29 '17 at 21:58
  • The matter is simple: we're odr-using n, hence it must be captured.That is the first bug. The second one is explained in your question. No matter what, n simply isn't an rvalue, and hence cannot initialize an rvalue reference (here). – Columbo Apr 29 '17 at 22:03
  • 4
    Yes, GCC is known to incorrectly do constant folding in lambda ultra-early. – T.C. Apr 29 '17 at 22:41
  • See, e.g., https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53157 – T.C. Apr 30 '17 at 08:34
  • 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 – Ivan Sanz Carasa Feb 08 '18 at 13:08
  • 4
    Works with gcc-8.1 – Michael Veksler Jul 25 '18 at 11:31

1 Answers1

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.

Ali
  • 288
  • 3
  • 10