Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can sizeof be applied inside a lambda on a variable that is not captured or is this a compiler bug?

Tags:

c++

c++11

This is a follow up of the discussion found here.

The following code compiles both under gcc and clang (live demo). This is surprising for the case in line //1 since the lambda does not capture anything. For the case of MCR2, where the lambda returns the pointer itself, we get the expected compile time error (line // Will not compile). How is application of operator sizeof different from returning the pointer?

#include <iostream>

#define MCR1(s) \
  ([]() { return sizeof(s); })()

#define MCR2(s) \
  ([]() { return s; })()

int main() {
  auto *s= "hello world";

  auto x1 = MCR1( s ); //1
  auto y1 = MCR1( "hello world" );
//  auto x2= MCR2( s ); // Will not compile
  auto y2= MCR2( "hello world" );

  std::cout << x1  << "  " << y1  << '\n';
  std::cout // << x2 << "  " 
            << y2 << '\n';
}

EDIT: Following up on the discussion here is another example. Surprisingly, the line marked //2 now compiles under gcc7 (development version) (live demo). The difference here is that expressions are marked constexpr, now.

#include <iostream>

#define MCR1(s) \
  ([]() { return sizeof(s); })()

#define MCR2(s) \
  ([]() { return s; })()

int main() {
  auto constexpr *s= "hello world";

  auto constexpr x1= MCR1( s );
  auto constexpr y1= MCR1( "hello world" );
  auto constexpr x2= MCR2( s );             //2
  auto constexpr y2= MCR2( "hello world" );

  std::cout << x1 << "  " << y1 << '\n';
  std::cout << x2 << "  " << y2 << '\n';
}
like image 549
Claas Bontus Avatar asked Nov 03 '16 16:11

Claas Bontus


People also ask

What is capture in lambda function?

A lambda expression can refer to identifiers declared outside the lambda expression. If the identifier is a local variable or a reference with automatic storage duration, it is an up-level reference and must be "captured" by the lambda expression.

How do you capture a variable in lambda C++?

To capture a variable by reference, we prepend an ampersand ( & ) to the variable name in the capture. Unlike variables that are captured by value, variables that are captured by reference are non-const, unless the variable they're capturing is const .

What is lambda capture list?

Capture clause A lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.

What does capture mean in C++?

It literally means to copy objects, (or references to objects) into the scope of the function. The function literally captures part of the environment from which it was created. This gives it context data to operate with.


2 Answers

The difference is the (lack of) evaluation of context. sizeof is unevaluated.

As per N3337 (≈C++11)

§5.1 2 [expr.prim.lambda] / 11

If a lambda-expression has an associated capture-default and its compound-statement odr-uses this or a variable with automatic storage duration and the odr-used entity is not explicitly captured, then the odr-used entity is said to be implicitly captured;

and

§5.1.2 [expr.prim.lambda] / 12

If a lambda-expression odr-uses this or a variable with automatic storage duration from its reaching scope, that entity shall be captured by the lambda-expression. If a lambda-expression captures an entity and that entity is not defined or captured in the immediately enclosing lambda expression or function, the program is ill-formed.

ODR use means use in potentially evaluated context:

§3.2 [basic.def.odr] / 2

An expression is potentially evaluated unless it is an unevaluated operand or a subexpression thereof. A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression and the lvalue-to-rvalue conversion is immediately applied

Since sizeof isn't, and s is in reaching scope of the lambda expression, it is okay. Returning s means evaluating it, though, and that's why it's ill-formed.

like image 161
krzaq Avatar answered Sep 19 '22 23:09

krzaq


Lambdas can "see" a lot of things in their surrounding scope without needing to capture them:

-- Global variables:

int x = 42;
int main() { []{ std::cout << x; }(); }

-- Static local variables:

int main() {
    static int x = 42;
    constexpr int y = 1337;
    []{ std::cout << x << y; }();
}

-- Functions:

int x() { return 42; }
int main() { []{ std::cout << x(); }(); }

-- Types:

using x = std::integral_constant<int, 42>;
int main() { []{ std::cout << x::value; }(); }

-- Local variables used in unevaluated contexts:

int main() {
    int x = 42;
    []{ std::cout << sizeof(x); }();
}

This just naturally falls out of the language rules. You can do the same thing in C++98 with a hand-written callable object:

int main() {
    int x = 42;
    struct functor {
        int operator()() const { return sizeof(x); }
    };
    std::cout << functor{}();
}

It's unsurprising, as sizeof does not evaluate its expression:

int main() {
    int x; // uninitialized
    using y = std::integral_constant<size_t, sizeof(x)>; // x used in a constant expression

    using z = std::integral_constant<size_t, 4>;
    static_assert(std::is_same<y, z>::value, "");

    std::cout << y::value;
}
like image 28
Oktalist Avatar answered Sep 22 '22 23:09

Oktalist