7

I want to only allow use of std::function in my code base if it does not do any allocations.

To this end I can write something like the function below and only use it to create my function instances:

template< typename Functor>
std::function<Functor> makeFunction( Functor f)
{
    return std::function<Functor>(std::allocator_arg, DummyAllocator(), f);
}

where DummyAllocator will assert or throw if it ever gets used at runtime.

Ideally though I would like to catch allocating use cases at compile time.

i.e.

template< typename Functor>
std::function<Functor> makeFunction( Functor f)
{
   static_assert( size needed for function to wrap f < space available in function, 
   "error - function will need to allocate memory");

   return std::function<Functor>(f);
 }

Is something like this possible?

dyp
  • 38,334
  • 13
  • 112
  • 177
David Woo
  • 749
  • 4
  • 13
  • 3
    I don't think the Small Object Optimization is exposed in `std::function`, relying on it will result in nonportable code anyway. E.g. libc++ uses a buffer the size of 3 `void*`, which is different on 32 vs 64 bit x86 systems. libc++ uses `sizeof(__buf_)` to check if the SOO shall be applied, and I can't find any function where this info is exposed. – dyp Aug 27 '15 at 16:19
  • @dyp Well, the code itself is portable. It will compile and produce expected results on all platforms. If the idea is to only allow the usage of non-allocating std::function, no matter what (and I can see some reasonable scenarios calling for that) this is sensible approach. Catching allocations at compile time is not possible, since they are not happening at compile time. – SergeyA Aug 27 '15 at 16:22
  • @SergeyA Well you could catch them at compile time if the remaining code of the ctor was constexpr, but that isn't possible with SOO because it must use placement-new. My remark about portability was more of an attempt at explaining *why* it isn't exposed. Even `int x = 1 << 17;` is nonportable code, and I certainly agree that the nonportability in the OP is of the best kind (compiler error). – dyp Aug 27 '15 at 16:27
  • @dyp I think, it is concievable to create an implementation of std::function which would call two different functions based on the size of arguments - one which would have calls to allocate() in it, and another that would not. So providing an allocator which does not implement an allocate() will ensure no allocations and introduce a compile-time check. – SergeyA Aug 27 '15 at 16:52
  • @SergeyA While that is *possible*, it is not guaranteed that an implementation uses tag dispatching. libc++ certainly does not, it uses a plain `if(sizeof(_FF) <= sizeof(__buf_) && is_nothrow_copy_constructible<_Fp>::value)`. It looks like libstdc++ uses tag dispatching, but I'm not entirely sure (there is`_Local_storage`, and it's used for tag dispatching, but I would have to look at the whole code to determine whether or not it *always* uses tag dispatching). – dyp Aug 27 '15 at 17:07
  • @dyp, by no means I was implying that such implememtation exists. I was just saing, that it possible, and thus might be wished for in the future standard (I can definitely see a benefit of it), or simply implemented personally by using library implementation with modifications. – SergeyA Aug 27 '15 at 17:09
  • 3
    You could write a `std::function` act-alike that insists on only storing its data within itself, [like this one](http://stackoverflow.com/a/32079802/1774667). – Yakk - Adam Nevraumont Aug 27 '15 at 17:22

3 Answers3

2

I'd write a std::function replacement that does not allocate, as std::function does allocate memory if it needs to, here's one candidate.

user1095108
  • 14,119
  • 9
  • 58
  • 116
2

The factory method you have is probably your best bet.

If not suitable, you may choose to implement an adaptor for function; implement the interface with the std::function as a member variable such that the adaptor enforces your constraints.

template <typename S>
class my_function {
  std::function<S> func_;
public:
  template <typename F>
  my_function(F&& f) :
  func_(std::allocator_arg, DummyAllocator(), std::forward<F>(f))
  {}
  // remaining functions required include operator()(...)
};
Niall
  • 30,036
  • 10
  • 99
  • 142
0

Given std::function allocator support in your library, simply provide std::function with an allocator that doesn't work.

template< typename t >
struct non_allocator : std::allocator< t > {
    t * allocate( std::size_t n ) { throw std::bad_alloc{}; }
    void deallocate( t * ) {}

    non_allocator() = default;
    template< typename u >
    non_allocator( non_allocator< u > const & ) {}

    template< typename u >
    struct rebind { typedef non_allocator< u > other; };
};

template< typename t, typename u >
bool operator == ( non_allocator< t > const &, non_allocator< t > const & )
    { return true; }

template< typename t, typename u >
bool operator != ( non_allocator< t > const &, non_allocator< t > const & )
    { return false; }

Unfortunately, this doesn't work in GCC because it does not even declare any allocator_arg constructors for function. Even in Clang, a compile-time error is impossible because it unfortunately uses a runtime if on a constant value to decide whether or not to use the allocator.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421