Search code examples
c++c++23

Deducing return type of a capturing lambda function `operator()` without an instantiated lambda variable


What I'm trying to do is to deduce return type of a capturing lambda from inside the class where the lambda is executed (never stored) so that the return type is not required to be part of class signature.

Since lambda is capturing the default constructor is deleted so I wrote such code:

using AppliedReturnType =
decltype
(
  std::apply
  (
    []( auto& ...items )
    {
      std::optional< ApplyLambda > lambda; // optional used here to work around construction

      return lambda.value()( items... ); // value used without initialization
    },
    t_
  )
);

Am I correct in assumption that since I'm using this code only in decltype evaluation I'd say that such code is not a problem no matter the std::optional implementation/UB at runtime?

And out of curiosity, is there a better way to do something like this? (not having the type in class template list is a must)

EDIT: editing simplified implementation a bit (based on the answers) so that it won't hurt so much the eyes of other readers that would copy paste code from such a question...

Below is a simplified implementation (extremely dumbed down as I'm certain that nobody would bother with 300+ lines of code...):

#include <iostream>
#include <optional>
#include <tuple>

template< typename Callable, typename Tuple >
class C
{
public:
  C( Callable&& callable, Tuple&& t )
    : t_{ std::move( t ) }
    , result_
      {
          std::apply
          (
            callable,
            t_
          )
      }
  {}
  
  auto result() const { return result_; }

private:
  Tuple t_;

  using AppliedReturnType =
    decltype
    (
      std::apply
      (
        []( auto& ...items )
        {
          std::optional< Callable > callable;

          return callable.value()( items... );
        },
        t_
      )
    );
    
  AppliedReturnType result_;
};

int main()
{
    int i = 10;
    
    auto lambda = [&i]( auto&... items ) { return ( items + ...) + i; };
    
    C c{ std::move( lambda ), std::tuple{ 1, 2, 3 } };
    
    std::cout << c.result() << '\n';
}

Solution

  • You are correct that since everything is in decltype, there is no issue, although you can simplify that stuff with

    using AppliedReturnType = decltype(std::apply(std::declval<ApplyLambda>(), t_));
    

    And as pointed out in the comments, you do not need that inner lambda in your constructor either

    C( ApplyLambda& lambda, Tuple&& t )
        : t_{ std::move( t ) }
        , result_{ std::apply( lambda, t_ ) }
      {}
    

    Demo