Is there something wrong with the code below?
#include <iostream>
#include <type_traits>
template <typename T>
void assign_lambda(T&& f)
{
typedef typename std::remove_reference<T>::type functor_type;
typedef typename std::aligned_storage<sizeof(functor_type),
std::alignment_of<functor_type>::value>::type buffer_type;
static char store[sizeof(buffer_type)];
auto const p(new (store) functor_type(std::forward<T>(f)));
(*p)();
}
int main()
{
for (int i(0); i != 5; ++i)
{
assign_lambda([i](){ std::cout << i << std::endl; });
}
return 0;
}
I worry though that this might be non-standard and/or dangerous to do.
EDIT:
Why initialize into a char array you ask? One might allocate a block of size sizeof(buffer_type) from the heap and reuse for repeated assignments (i.e. avoid repeated memory allocations), if the block should prove large enough.
void*operator new(std::size_t size);
Effects: The allocation function (3.7.4.1) called by a new-expression (5.3.4) to allocate size bytes of storage suitably aligned to represent any object of that size.
I suppose if I allocate from the heap the alignment issues will go away.
You'll have to make sure that store has the proper alignment for functor_type. Apart from that, I don't see any problems regarding standard conformance. However, you can easily address the multithreading issue by making the array nonstatic, because sizeof gives a compiletime constant.
The alignment is demanded by §5.3.4,14:
[ Note: when the allocation function returns a value other than null, it must be a pointer to a block of storage in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned and of the requested size. [...] -end note ]
There is another paragraph, §3.7.4.1 about alignment, but that one does explicitly not apply to placement new (§18.6.1.3,1).
To get the alignment right, you can do the following:
template <typename T>
void assign_lambda(T&& f)
{
typedef typename std::remove_reference<T>::type functor_type;
//alignas(functor_type) char store[sizeof(functor_type)];
std::aligned_storage<sizeof(functor_type),
std::alignment_of<functor_type>::value>::type store;
auto const p(new (&store) functor_type(std::forward<T>(f)));
(*p)();
//"placement delete"
p->~functor_type();
}
Update: The approach shown above is not different from using just a normal variable:
template <typename T>
void assign_lambda(T&& f)
{
typedef typename std::remove_reference<T>::type functor_type;
functor_type func{std::forward<T>(f)};
func();
}
If it has to be a static variable inside the function you will need an RAII wrapper for functors that are not assignable. Just placement-newing is not sufficient since the functors will not get destroyed properly and ressources they possess (e.g. via captured smartpointers) will not get released.
template <typename F>
struct RAIIFunctor {
typedef typename std::remove_reference<F>::type functor_type;
std::aligned_storage<sizeof(functor_type),
std::alignment_of<functor_type>::value>::type store;
functor_type* f;
RAIIFunctor() : f{nullptr} {}
~RAIIFunctor() { destroy(); }
template <class T>
void assign(T&& t) {
destroy();
f = new(&store) functor_type {std::forward<T>(t)};
}
void destroy() {
if (f)
f->~functor_type();
f = nullptr;
}
void operator() {
(*f)();
}
};
template <typename T>
void assign_lambda(T&& f)
{
static RAIIFunctor<T> func;
func.assign(std::forward<T>(f));
func();
}
You can see the code in action here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With