Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lambda functors assignment workaround

Tags:

c++

c++11

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.

like image 407
user1095108 Avatar asked Jun 04 '26 23:06

user1095108


1 Answers

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

like image 105
Arne Mertz Avatar answered Jun 06 '26 13:06

Arne Mertz