Date: Tue, 20 May 2025 08:28:35 +0100
On Sun, May 18, 2025 at 8:54 PM Jeremy Rifkin wrote:
>
> > you can just have an 'explicit this' parameter to the lambda
>
> In which case you'd never use std::recurse.
You have me there.
So let me just try sum things up again:
(1) If the lambda's operator() is a non-static member function,
then just use the 'explicit this' syntax to gain access to the
lambda's type and also the lambda object's 'this' pointer. You can
make a recursive call by invoking operator() on the 'self'.
(2) If the lambda's operator() is a static member function, then
inside the lambda we've got no way of getting the lambda's type. There
is no 'this' pointer. No way of making a recursive call.
So let's start off with a lambda that has a non-static operator() as follows:
#include <iostream>
int main(void)
{
auto mylambda = [](this auto &&self, int const arg)
{
if ( arg < 0 ) return;
std::cout << arg << std::endl;
self(arg - 1u);
};
mylambda(8);
}
If we use the '+' operator to try turn that lambda into a function
pointer, it fails to compile. Normally we only get this compiler error
when the lambda has captures, but on this occasion it's only failing
to compile because we have an 'explicit this' parameter. So the lambda
object's 'this' pointer is actually not needed. We could invoke the
lambda on a nullptr and it should work fine.
Therefore it's possible to turn the lambda into a normal function
pointer and then just set the 'this' pointer to nullptr. I spent hours
trying to get this to work with "bind_front" and so forth, but kept
winding up with a function object instead of a function pointer, so I
gave up eventually. Then I thought back to code that Alf P. Steinbach
posted to comp.lang.c++ a few years ago, as well as code that the late
Edward Catmur posted here on this mailing list. And so if you want to
have your cake and eat it too: i.e. have a recursive lambda that you
can also use as a normal function pointer, then here's what I've
wrangled:
https://godbolt.org/z/jbK69x3hT
It's a hell of a lot of tinkering to achieve something simple. A
preferable solution would have been to have a standard library
function such as "std::recurse" or "std::addr_func" to allow the
lambda to recurse without needing an explicit 'this' parameter.
And here's all the GodBolt code copy-pasted:
#include <cassert> // assert
#include <cstddef> // size_t
#include <cstdlib> // abort
#include <mutex> // mutex, lock_guard
#include <utility> // index_sequence, make_index_sequence, forward, declval
// The next four templates: 'ToFuncPtr', 'ToReturnType', 'IsNoExcept',
'LambdaOperatorAddress'
// are just helpers to make this all possible. You can scroll past
them to Line #107
namespace detail {
// The following template turns a member function pointer into a
// normal function pointer, but we only want it to use decltype on it
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
ReturnType (*ToFuncPtr(ReturnType (ClassType::*)(Params...) const
noexcept(b_noexcept)))(Params...) noexcept(b_noexcept)
{
return nullptr; // suppress compiler warning
}
// and also the non-const version for non-const member functions (or
mutable lambdas):
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
ReturnType (*ToFuncPtr(ReturnType (ClassType::*)(Params...)
noexcept(b_noexcept)))(Params...) noexcept(b_noexcept)
{
return nullptr; // suppress compiler warning
}
// and a version for member functions with 'explicit this-parameter'
template<typename ReturnType, class ClassType = void, bool b_noexcept,
typename... Params>
ReturnType (*ToFuncPtr(ReturnType(*)(ClassType&, Params...)
noexcept(b_noexcept)))(Params...) noexcept(b_noexcept)
{
return nullptr; // suppress compiler warning
}
// The following template isolates the return type of a member function pointer,
// but we only want it to use decltype on it. I tried using 'std::result_of_t'
// instead but I get a compiler error for the lambda being an incomplete type
template <typename ReturnType, class ClassType, typename... Params>
ReturnType ToReturnType(ReturnType (ClassType::*)(Params...) const)
{
return std::declval<ReturnType>(); // suppress compiler warning
}
// and also the non-const version for non-const member functions (or
mutable lambdas):
template <typename ReturnType, class ClassType, typename... Params>
ReturnType ToReturnType(ReturnType (ClassType::*)(Params...))
{
return std::declval<ReturnType>(); // suppress compiler warning
}
// and a version for member functions with 'explicit this-parameter'
template <typename ReturnType, class ClassType = void, typename... Params>
ReturnType ToReturnType( ReturnType (*)(ClassType&, Params...) )
{
return std::declval<ReturnType>(); // suppress compiler warning
}
// The following template determines whether a non-static member
function is noexcept
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
consteval bool IsNoExcept(ReturnType (ClassType::*)(Params...) const
noexcept(b_noexcept))
{
return b_noexcept;
}
// and also the non-const version for non-const member functions (or
mutable lambdas):
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
consteval bool IsNoExcept(ReturnType (ClassType::*)(Params...)
noexcept(b_noexcept))
{
return b_noexcept;
}
// and a version for member functions with 'explicit this-parameter'
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
consteval bool IsNoExcept(ReturnType(*)(ClassType&,Params...)
noexcept(b_noexcept))
{
return b_noexcept;
}
template<typename T>
struct LambdaOperatorAddress {
static consteval auto get(void) noexcept
{
if constexpr ( requires { &T::operator(); } )
{
return &T::operator();
}
else
{
// If it's a template lambda, i.e if it's like:
// struct Lambda {
// template<typename T>
// int operator()(T &&arg)
// {
// return 6 + arg;
// }
// };
// or it's got an "explicit this" parameter:
// stuct Lambda {
// int operator()(this auto &&self, int i)
// {
// if ( i <= 0 ) return 0;
// return self(--i);
// }
// };
return &T::template operator()<T&>;
}
}
typedef decltype(get()) type;
};
} // close namespace 'detail'
template<typename LambdaType,std::size_t N = 32u>
class thunk {
protected:
using size_t = std::size_t;
using R =
decltype(detail::ToReturnType(detail::LambdaOperatorAddress<LambdaType>::get()));
using FuncPtr = decltype(detail::ToFuncPtr
(detail::LambdaOperatorAddress<LambdaType>::get())); // preserves the
'noexcept'
inline static LambdaType *data[N] = {}; // starts off all nullptr's
inline static std::mutex mut;
size_t const index;
public:
explicit thunk(LambdaType &arg) noexcept : index(acquire(arg))
{
data[index] = &arg;
}
explicit thunk(LambdaType &&arg) noexcept : thunk(arg) {} // also
accept an R-value
~thunk(void) noexcept { release(index); }
FuncPtr get(void) const noexcept
{
FuncPtr ret;
[this,&ret]<size_t... I>(std::index_sequence<I...>)
{
((I == index ? ret = &invoke<I> : ret), ...);
}(std::make_index_sequence<N>());
return ret;
}
operator FuncPtr(void) const noexcept { return this->get(); }
protected:
static size_t acquire(LambdaType &arg) noexcept
{
std::lock_guard lck(mut); // might throw std::system_error -
std::terminate
for ( size_t i = 0; i < N; ++i ) if ( nullptr == data[i] )
return ((data[i] = &arg), i);
assert( nullptr == "thunk pool exhausted" );
std::abort(); // ifdef NDEBUG
}
static void release(size_t const i) noexcept
{
std::lock_guard lck(mut); // might throw std::system_error -
std::terminate
data[i] = nullptr;
}
template<size_t I, typename... A> static R invoke(A... a)
noexcept(detail::IsNoExcept(detail::LambdaOperatorAddress<LambdaType>::get()))
{
assert( nullptr != data[I] );
return (*data[I])(std::forward<A>(a)...);
}
thunk(void) = delete;
thunk(thunk const & ) = delete;
thunk(thunk &&) = delete;
thunk &operator=(thunk const & ) = delete;
thunk &operator=(thunk &&) = delete;
thunk const *operator&(void) const = delete; // just to avoid confusion
thunk *operator&(void) = delete; // just to avoid confusion
};
// ========================================================
// And now the test code.............
// ========================================================
#include <iostream> // cout, endl
using std::cout, std::endl;
extern "C" bool SomeLibraryFunc( void (*arg)(int) ) // Can only
accept a function pointer
{
arg(7);
return true;
}
int main(int argc, char **argv)
{
auto f = [](this auto &&self, int i)
{
if ( i <= 0 ) return;
cout << i << endl;
self(--i);
};
SomeLibraryFunc( thunk(f) );
}
>
> > you can just have an 'explicit this' parameter to the lambda
>
> In which case you'd never use std::recurse.
You have me there.
So let me just try sum things up again:
(1) If the lambda's operator() is a non-static member function,
then just use the 'explicit this' syntax to gain access to the
lambda's type and also the lambda object's 'this' pointer. You can
make a recursive call by invoking operator() on the 'self'.
(2) If the lambda's operator() is a static member function, then
inside the lambda we've got no way of getting the lambda's type. There
is no 'this' pointer. No way of making a recursive call.
So let's start off with a lambda that has a non-static operator() as follows:
#include <iostream>
int main(void)
{
auto mylambda = [](this auto &&self, int const arg)
{
if ( arg < 0 ) return;
std::cout << arg << std::endl;
self(arg - 1u);
};
mylambda(8);
}
If we use the '+' operator to try turn that lambda into a function
pointer, it fails to compile. Normally we only get this compiler error
when the lambda has captures, but on this occasion it's only failing
to compile because we have an 'explicit this' parameter. So the lambda
object's 'this' pointer is actually not needed. We could invoke the
lambda on a nullptr and it should work fine.
Therefore it's possible to turn the lambda into a normal function
pointer and then just set the 'this' pointer to nullptr. I spent hours
trying to get this to work with "bind_front" and so forth, but kept
winding up with a function object instead of a function pointer, so I
gave up eventually. Then I thought back to code that Alf P. Steinbach
posted to comp.lang.c++ a few years ago, as well as code that the late
Edward Catmur posted here on this mailing list. And so if you want to
have your cake and eat it too: i.e. have a recursive lambda that you
can also use as a normal function pointer, then here's what I've
wrangled:
https://godbolt.org/z/jbK69x3hT
It's a hell of a lot of tinkering to achieve something simple. A
preferable solution would have been to have a standard library
function such as "std::recurse" or "std::addr_func" to allow the
lambda to recurse without needing an explicit 'this' parameter.
And here's all the GodBolt code copy-pasted:
#include <cassert> // assert
#include <cstddef> // size_t
#include <cstdlib> // abort
#include <mutex> // mutex, lock_guard
#include <utility> // index_sequence, make_index_sequence, forward, declval
// The next four templates: 'ToFuncPtr', 'ToReturnType', 'IsNoExcept',
'LambdaOperatorAddress'
// are just helpers to make this all possible. You can scroll past
them to Line #107
namespace detail {
// The following template turns a member function pointer into a
// normal function pointer, but we only want it to use decltype on it
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
ReturnType (*ToFuncPtr(ReturnType (ClassType::*)(Params...) const
noexcept(b_noexcept)))(Params...) noexcept(b_noexcept)
{
return nullptr; // suppress compiler warning
}
// and also the non-const version for non-const member functions (or
mutable lambdas):
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
ReturnType (*ToFuncPtr(ReturnType (ClassType::*)(Params...)
noexcept(b_noexcept)))(Params...) noexcept(b_noexcept)
{
return nullptr; // suppress compiler warning
}
// and a version for member functions with 'explicit this-parameter'
template<typename ReturnType, class ClassType = void, bool b_noexcept,
typename... Params>
ReturnType (*ToFuncPtr(ReturnType(*)(ClassType&, Params...)
noexcept(b_noexcept)))(Params...) noexcept(b_noexcept)
{
return nullptr; // suppress compiler warning
}
// The following template isolates the return type of a member function pointer,
// but we only want it to use decltype on it. I tried using 'std::result_of_t'
// instead but I get a compiler error for the lambda being an incomplete type
template <typename ReturnType, class ClassType, typename... Params>
ReturnType ToReturnType(ReturnType (ClassType::*)(Params...) const)
{
return std::declval<ReturnType>(); // suppress compiler warning
}
// and also the non-const version for non-const member functions (or
mutable lambdas):
template <typename ReturnType, class ClassType, typename... Params>
ReturnType ToReturnType(ReturnType (ClassType::*)(Params...))
{
return std::declval<ReturnType>(); // suppress compiler warning
}
// and a version for member functions with 'explicit this-parameter'
template <typename ReturnType, class ClassType = void, typename... Params>
ReturnType ToReturnType( ReturnType (*)(ClassType&, Params...) )
{
return std::declval<ReturnType>(); // suppress compiler warning
}
// The following template determines whether a non-static member
function is noexcept
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
consteval bool IsNoExcept(ReturnType (ClassType::*)(Params...) const
noexcept(b_noexcept))
{
return b_noexcept;
}
// and also the non-const version for non-const member functions (or
mutable lambdas):
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
consteval bool IsNoExcept(ReturnType (ClassType::*)(Params...)
noexcept(b_noexcept))
{
return b_noexcept;
}
// and a version for member functions with 'explicit this-parameter'
template<typename ReturnType, class ClassType, bool b_noexcept,
typename... Params>
consteval bool IsNoExcept(ReturnType(*)(ClassType&,Params...)
noexcept(b_noexcept))
{
return b_noexcept;
}
template<typename T>
struct LambdaOperatorAddress {
static consteval auto get(void) noexcept
{
if constexpr ( requires { &T::operator(); } )
{
return &T::operator();
}
else
{
// If it's a template lambda, i.e if it's like:
// struct Lambda {
// template<typename T>
// int operator()(T &&arg)
// {
// return 6 + arg;
// }
// };
// or it's got an "explicit this" parameter:
// stuct Lambda {
// int operator()(this auto &&self, int i)
// {
// if ( i <= 0 ) return 0;
// return self(--i);
// }
// };
return &T::template operator()<T&>;
}
}
typedef decltype(get()) type;
};
} // close namespace 'detail'
template<typename LambdaType,std::size_t N = 32u>
class thunk {
protected:
using size_t = std::size_t;
using R =
decltype(detail::ToReturnType(detail::LambdaOperatorAddress<LambdaType>::get()));
using FuncPtr = decltype(detail::ToFuncPtr
(detail::LambdaOperatorAddress<LambdaType>::get())); // preserves the
'noexcept'
inline static LambdaType *data[N] = {}; // starts off all nullptr's
inline static std::mutex mut;
size_t const index;
public:
explicit thunk(LambdaType &arg) noexcept : index(acquire(arg))
{
data[index] = &arg;
}
explicit thunk(LambdaType &&arg) noexcept : thunk(arg) {} // also
accept an R-value
~thunk(void) noexcept { release(index); }
FuncPtr get(void) const noexcept
{
FuncPtr ret;
[this,&ret]<size_t... I>(std::index_sequence<I...>)
{
((I == index ? ret = &invoke<I> : ret), ...);
}(std::make_index_sequence<N>());
return ret;
}
operator FuncPtr(void) const noexcept { return this->get(); }
protected:
static size_t acquire(LambdaType &arg) noexcept
{
std::lock_guard lck(mut); // might throw std::system_error -
std::terminate
for ( size_t i = 0; i < N; ++i ) if ( nullptr == data[i] )
return ((data[i] = &arg), i);
assert( nullptr == "thunk pool exhausted" );
std::abort(); // ifdef NDEBUG
}
static void release(size_t const i) noexcept
{
std::lock_guard lck(mut); // might throw std::system_error -
std::terminate
data[i] = nullptr;
}
template<size_t I, typename... A> static R invoke(A... a)
noexcept(detail::IsNoExcept(detail::LambdaOperatorAddress<LambdaType>::get()))
{
assert( nullptr != data[I] );
return (*data[I])(std::forward<A>(a)...);
}
thunk(void) = delete;
thunk(thunk const & ) = delete;
thunk(thunk &&) = delete;
thunk &operator=(thunk const & ) = delete;
thunk &operator=(thunk &&) = delete;
thunk const *operator&(void) const = delete; // just to avoid confusion
thunk *operator&(void) = delete; // just to avoid confusion
};
// ========================================================
// And now the test code.............
// ========================================================
#include <iostream> // cout, endl
using std::cout, std::endl;
extern "C" bool SomeLibraryFunc( void (*arg)(int) ) // Can only
accept a function pointer
{
arg(7);
return true;
}
int main(int argc, char **argv)
{
auto f = [](this auto &&self, int i)
{
if ( i <= 0 ) return;
cout << i << endl;
self(--i);
};
SomeLibraryFunc( thunk(f) );
}
Received on 2025-05-20 07:28:47