6

Is it legal to call the pointer which points to a lambda that does not exist anymore?

Here is the demo code snippet.

#include <iostream>

typedef int (*Func)(int a);

int main()
{
 Func fun;

 {
    auto lambda = [](int a)->int{std::cout << a << std::endl; return a;};   
    fun =lambda;
 }

 fun(6); //Is it legal? The variable lambda does not exist anymore.
}

What about generic condition, say lambdas which have captures?

John
  • 2,963
  • 11
  • 33
  • 4
    Lambdas with captures are not convertible to a function pointer. So you'd need to generalize more precisely. The first well defined question is already asked https://stackoverflow.com/q/49666616/817643 – StoryTeller - Unslander Monica Aug 24 '23 at 11:15
  • 1
    If a lambda has a capture, it simply won't have a conversion to pointer to function, so the `fun = lambda` simply won't compile. – Jerry Coffin Aug 24 '23 at 11:16

2 Answers2

2

Yes, this is legal. Relevant text:

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.

The conversion function is called as part of the evaluation of fun = lambda;, where lambda obviously is in scope. The value returned by this conversion function is stored in fun, which does not go out of scope when lambda does. Also, the value returned is a function address, and functions do not have a lifetime at all. (They behave as if they have static storage duration).

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • The quote means to me that given a captureless lambda object L, `ptr = L; ptr()` is defined to "have the same effect" as `L()`. (They don't say the same effect as the call operator on an invented default-constructed lambda, or an invented lambda copy.) So ... One has to say why one can call a non-static member function with an implicit object parameter outside its lifetime (pre-C++23) or call a static member function through an id-expression naming an object outside its lifetime (C++23). – Jeff Garrett Aug 24 '23 at 17:51
  • I suspect in the C++23 case, one can find the wording. It is not clear to me that it is OK before C++23, despite the reasonableness of it. This is CWG1937: https://cplusplus.github.io/CWG/issues/1937.html – Jeff Garrett Aug 24 '23 at 17:51
  • @JeffGarrett: The point is that you're not calling a *member* function. You are calling a function that **has the same effect**, by definition. – MSalters Aug 25 '23 at 06:47
  • Sure, but the thing you want the same effect as (1) requires an object to perform (before C++23), (2) it is unspecified which object to use, (3) there is only one object in sight, and (4) the effect is UB if the object is not within its lifetime. To put it another way, it by definition has the same effect as calling the operator /with which object/? Recall that the closure type would not have been default-constructible in C++11, so you can't just default construct one before C++20. – Jeff Garrett Aug 25 '23 at 12:11
  • Here's the C++20 wording: https://timsong-cpp.github.io/cppwp/n4861/expr.prim.lambda#closure-7 – Jeff Garrett Aug 25 '23 at 12:25
  • @JeffGarrett: So C++20 is the same. It states that there is a function `F` which has the same effect as the non-capturing lambda. Note that this is the Standard, and it imposes restrictions on the C++ implementation. Since the Stadard does not state that this function `F` depends on the lifetime of an object, it does not - only the Standard could make it UB, and it doesn't. – MSalters Aug 25 '23 at 12:49
  • We'll have to agree to disagree. By the text of the standard before C++20 (and without some change to try to apply the intent of CWG1937) I would say it can be UB. Standard defines it in terms of another expression which depends on lifetime. Therefore, it does. This is why there was the CWG issue and the text changed in C++20. They wanted to change that. – Jeff Garrett Aug 25 '23 at 12:57
1

This code is legal. As you can see if you use the site https://cppinsights.io/ the code above is actually translated to this:

#include <iostream>

typedef int (*Func)(int a);

int main()
{
  using FuncPtr_7 = Func;
  FuncPtr_7 fun;
  {
        
    class __lambda_10_19
    {
      public: 
      inline /*constexpr */ int operator()(int a) const
      {
        std::cout.operator<<(a).operator<<(std::endl);
        return a;
      }
      
      using retType_10_19 = auto (*)(int) -> int;
      inline constexpr operator retType_10_19 () const noexcept
      {
        return __invoke;
      }
      
      private: 
      static inline /*constexpr */ int __invoke(int a)
      {
        return __lambda_10_19{}.operator()(a);
      }
      
      
    };
    
    __lambda_10_19 lambda = __lambda_10_19{};
    fun = static_cast<int (*)(int)>(lambda.operator __lambda_10_19::retType_10_19());
  };
  fun(6);
  return 0;
}

so fun points to a static function (__invoke), which does not go out of scope.

  • 1
    This does not answer the question. You show one implementation where it works. That doesn't mean it is legal. – MSalters Aug 24 '23 at 14:12
  • 1
    `fun` points to a static function (`__invoke`), **but `__invoke` calls `operator()(int a)` which is a member function other than a stactic function.** Could you please explain that more to me? – John Aug 25 '23 at 02:46
  • @John function ```__invoke``` calls operator () on a temporary object which it creates – Don'tDownVote Aug 25 '23 at 10:12