Static Lambdas: Initializing lambdas at compile time
If we want to initialize a lambda as a global variable, we could initialize it like this:
const auto add_one = [](auto x)
{
return x + 1;
};
However, this is initialized at program start-up, so we can run into problems with static initialization order fiasco. One of ways to workaround this is to initialize it at compile-time instead. We could try using constexpr
to do this:
const constexpr auto add_one = [](auto x)
{
return x + 1;
};
However, this will fail, because the lambda is not a constexpr
expression, but we can use a trick by Richard Smith to use a lambda in constexpr
expressions:
template<typename T>
typename std::remove_reference<T>::type *addr(T &&t)
{
return &t;
}
const constexpr auto add_one = true ? nullptr : addr([](auto x)
{
return x + 1;
});
This works because the false branch is never taken, so just a nullptr
will be returned, but the type will still be deduced as a pointer to the lambda.
However, now we just have a null pointer to our lambda which in not very useful. Now, we could just dereference the pointer, but that makes a lot people uncomfortable(we’ll save that for another discussion). Instead, we can assume that the lambda will always be empty, thus, we can just do a cast from another empty type, since they both have the same memory layout. So we build a wrapper
class that makes a default constructible function object:
template<class F>
struct wrapper
{
static_assert(std::is_empty<F>(), "Lambdas must be empty");
template<class... Ts>
decltype(auto) operator()(Ts&&... xs) const
{
return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
}
};
Now a static assert is added to make sure that the lambda is always empty. This should always be the case(since its required to decay to a function pointer), but the standard doesn’t explicitly guarantee it. So we use the assert to at least catch the insane implementation of lambdas. Then we just cast the wrapper
class to the lambda since both of them are empty.
Now putting it together we can now initialize our lambda using constexpr
:
template<class F>
constexpr wrapper<F> make_function(F*)
{
return {};
}
const constexpr auto add_one = make_function(true ? nullptr : addr([](auto x)
{
return x + 1;
}));
However, thats a lot of noise to initialize a lambda. Of course, we can make a simple macro to help with this. First, lets define the make_function
and addr
using some operators so we don’t have to define the lambda body in a macro:
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return {};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t)
{
return &t;
}
};
Now, we can create our macro like this:
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
const constexpr auto add_one = STATIC_LAMBDA(auto x)
{
return x + 1;
};