Skip to content

Instantly share code, notes, and snippets.

@glampert
Created May 15, 2017 00:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glampert/c42106690da29f04b83602bbfb119ab1 to your computer and use it in GitHub Desktop.
Save glampert/c42106690da29f04b83602bbfb119ab1 to your computer and use it in GitHub Desktop.
std::function-like functor with an inline buffer that never dynamically allocates. Also doesn't use C++ exceptions.
#pragma once
// ============================================================================
// About:
// std::function-like functor that has an inline buffer to store the
// callable object. It is guaranteed to never allocate. If the callable
// is too big, a static_assert is triggered. The size of the functor is
// defined as a template argument, so it can be of any size. It also doesn't
// use C++ exceptions.
//
// License:
// Public Domain.
// Feel free to copy, distribute, and modify this file as you see fit.
// ============================================================================
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
// Configurable, but should be a power of two.
#ifndef INPLACE_FUNCTION_ALIGN
#define INPLACE_FUNCTION_ALIGN 16
#endif // INPLACE_FUNCTION_ALIGN
// Used to trap null function calls. Exceptions are not used.
#ifndef INPLACE_FUNCTION_ASSERT
#include <cassert>
#define INPLACE_FUNCTION_ASSERT(expr, message) assert(expr && message)
#endif // INPLACE_FUNCTION_ASSERT
// ========================================================
// class InPlaceFunctionBase:
// ========================================================
template<int, typename>
class InPlaceFunctionBase; // Unimplemented
template<int SizeInBytes, typename ReturnType, typename... ArgTypes>
class InPlaceFunctionBase<SizeInBytes, ReturnType(ArgTypes...)>
{
private:
struct BaseCallableObject
{
virtual ReturnType invoke(ArgTypes... args) const = 0;
virtual BaseCallableObject * copyConstruct(void * where) const = 0;
virtual BaseCallableObject * moveConstruct(void * where) = 0;
virtual ~BaseCallableObject() = default;
};
template<typename F>
struct CallableObjectImpl final
: public BaseCallableObject
{
F m_fn;
CallableObjectImpl(const F & fn)
: m_fn{ fn }
{ }
CallableObjectImpl(F && fn)
: m_fn{ std::move(fn) }
{ }
ReturnType invoke(ArgTypes... args) const override
{
return m_fn(std::forward<ArgTypes>(args)...);
}
BaseCallableObject * copyConstruct(void * where) const override
{
return ::new(where) CallableObjectImpl<F>{ m_fn };
}
BaseCallableObject * moveConstruct(void * where) override
{
return ::new(where) CallableObjectImpl<F>{ std::move(m_fn) };
}
};
enum Constants
{
kUserAlign = INPLACE_FUNCTION_ALIGN,
kMinAlign = (kUserAlign > alignof(std::max_align_t)) ? kUserAlign : alignof(std::max_align_t),
kMinSize = (sizeof(BaseCallableObject *) + (kMinAlign - 1)) & ~(kMinAlign - 1),
kMaxSize = SizeInBytes - sizeof(BaseCallableObject *),
};
static_assert(SizeInBytes >= kMinSize, "Requested InPlaceFunction size is too small.");
union Storage
{
struct FunctionObject
{
// Space for a lambda capture, etc, minus the reserved callable pointer.
alignas(kMinAlign) std::uint8_t buffer[kMaxSize];
// One pointer worth of memory is taken from the end for the functor pointer.
BaseCallableObject * callablePtr;
} funcObj;
// In case INPLACE_FUNCTION_ALIGN is less than the minimum, don't rely solely on alignas().
std::max_align_t maxAlign;
} m_storage;
protected:
void * placementBuffer()
{
return m_storage.funcObj.buffer;
}
BaseCallableObject * callablePtr() const
{
return m_storage.funcObj.callablePtr;
}
void setCallablePtr(BaseCallableObject * callable)
{
m_storage.funcObj.callablePtr = callable;
}
template<typename F> void placeFunctionObject(F && fn)
{
static_assert(sizeof(F) <= kMaxSize, "Function object too big to fit in InPlaceFunction! Use a bigger size.");
CallableObjectImpl<F> temp{ std::move(fn) };
setCallablePtr(temp.moveConstruct(placementBuffer()));
}
void placeMove(InPlaceFunctionBase * other)
{
BaseCallableObject * otherCallable = other->callablePtr();
if (otherCallable != nullptr)
{
setCallablePtr(otherCallable->moveConstruct(placementBuffer()));
other->setCallablePtr(nullptr);
}
}
void placeCopy(const InPlaceFunctionBase & other)
{
const BaseCallableObject * otherCallable = other.callablePtr();
if (otherCallable != nullptr)
{
setCallablePtr(otherCallable->copyConstruct(placementBuffer()));
}
}
void destroy()
{
BaseCallableObject * myCallable = callablePtr();
if (myCallable != nullptr)
{
myCallable->~BaseCallableObject();
setCallablePtr(nullptr);
}
}
public:
InPlaceFunctionBase()
{
setCallablePtr(nullptr);
}
~InPlaceFunctionBase()
{
destroy();
}
bool isNull() const
{
return callablePtr() == nullptr;
}
ReturnType operator()(ArgTypes... args) const
{
INPLACE_FUNCTION_ASSERT(!isNull(), "Invalid InPlaceFunction call!");
return callablePtr()->invoke(std::forward<ArgTypes>(args)...);
}
};
// ========================================================
// class InPlaceFunction:
// ========================================================
template<int SizeInBytes, typename FuncType>
class InPlaceFunction final
: public InPlaceFunctionBase<SizeInBytes, FuncType>
{
public:
//
// Constructors:
//
InPlaceFunction() = default;
InPlaceFunction(std::nullptr_t) { } // Implicitly null.
InPlaceFunction(InPlaceFunction && other)
{
placeMove(&other);
}
InPlaceFunction(const InPlaceFunction & other)
{
placeCopy(other);
}
template<typename F> InPlaceFunction(F fn)
{
placeFunctionObject(std::move(fn));
}
//
// Assignment:
//
InPlaceFunction & operator = (std::nullptr_t)
{
destroy();
return *this;
}
InPlaceFunction & operator = (InPlaceFunction && other)
{
destroy();
placeMove(&other);
return *this;
}
InPlaceFunction & operator = (const InPlaceFunction & other)
{
destroy();
placeCopy(other);
return *this;
}
template<typename F> InPlaceFunction & operator = (F fn)
{
destroy();
placeFunctionObject(std::move(fn));
return *this;
}
//
// Null pointer comparison:
//
explicit operator bool() const
{
return !isNull();
}
bool operator == (std::nullptr_t) const
{
return isNull();
}
bool operator != (std::nullptr_t) const
{
return !isNull();
}
// Disallow copying or assigning from a function of different size.
template<int OtherSize> InPlaceFunction(const InPlaceFunction<OtherSize, FuncType> &) = delete;
template<int OtherSize> InPlaceFunction & operator = (const InPlaceFunction<OtherSize, FuncType> &) = delete;
};
// ========================================================
// Aliases for common sizes:
// ========================================================
template<typename F> using InPlaceFunction16 = InPlaceFunction< 16, F>;
template<typename F> using InPlaceFunction32 = InPlaceFunction< 32, F>;
template<typename F> using InPlaceFunction64 = InPlaceFunction< 64, F>;
template<typename F> using InPlaceFunction128 = InPlaceFunction<128, F>;
template<typename F> using InPlaceFunction256 = InPlaceFunction<256, F>;
template<typename F> using InPlaceFunction512 = InPlaceFunction<512, F>;
static_assert(sizeof(InPlaceFunction16<void()>) == 16, "Wrong size for InPlaceFunction16");
static_assert(sizeof(InPlaceFunction32<void()>) == 32, "Wrong size for InPlaceFunction32");
static_assert(sizeof(InPlaceFunction64<void()>) == 64, "Wrong size for InPlaceFunction64");
static_assert(sizeof(InPlaceFunction128<void()>) == 128, "Wrong size for InPlaceFunction128");
static_assert(sizeof(InPlaceFunction256<void()>) == 256, "Wrong size for InPlaceFunction256");
static_assert(sizeof(InPlaceFunction512<void()>) == 512, "Wrong size for InPlaceFunction512");
// ========================================================
// class MemberFunctionHolder:
// ========================================================
//
// Allows easily creating an InPlaceFunction from a
// pointer to an object and class member function.
// Use the provided makeInPlaceFunctionFromMember()
// functions to create the wrappers. The function
// type returned will be an InPlaceFunction32. No
// additional memory allocated. Example:
//
// MyClass obj{};
// auto memFun = makeInPlaceFunctionFromMember(&obj, &MyClass::someMethod);
// memFun(); // calls obj->someMethod();
//
template<typename ClassType, typename ReturnType, typename... ArgTypes>
class MemberFunctionHolder final
{
public:
// This selector will use the const-qualified method if
// ClassType is const, the non-const one otherwise.
using MMemFunc = ReturnType (ClassType::*)(ArgTypes...);
using CMemFunc = ReturnType (ClassType::*)(ArgTypes...) const;
using MemFuncType = typename std::conditional<std::is_const<ClassType>::value, CMemFunc, MMemFunc>::type;
MemberFunctionHolder(ClassType * obj, MemFuncType pmf)
: m_pObject(obj), m_pMemFun(pmf)
{
INPLACE_FUNCTION_ASSERT(m_pObject != nullptr, "Null this pointer!");
INPLACE_FUNCTION_ASSERT(m_pMemFun != nullptr, "Null member pointer!");
}
ReturnType operator()(ArgTypes... args) const
{
return (m_pObject->*m_pMemFun)(std::forward<ArgTypes>(args)...);
}
private:
ClassType * m_pObject;
MemFuncType m_pMemFun;
};
// ========================================================
template<typename ClassType, typename ReturnType, typename... ArgTypes>
inline InPlaceFunction32<ReturnType(ArgTypes...)>
makeInPlaceFunctionFromMember(ClassType * obj, ReturnType (ClassType::*pmf)(ArgTypes...))
{
return InPlaceFunction32<ReturnType(ArgTypes...)>(
MemberFunctionHolder<ClassType, ReturnType, ArgTypes...>(obj, pmf));
}
template<typename ClassType, typename ReturnType, typename... ArgTypes>
inline InPlaceFunction32<ReturnType(ArgTypes...)>
makeInPlaceFunctionFromMember(const ClassType * obj, ReturnType (ClassType::*pmf)(ArgTypes...) const)
{
return InPlaceFunction32<ReturnType(ArgTypes...)>(
MemberFunctionHolder<const ClassType, ReturnType, ArgTypes...>(obj, pmf));
}
// ========================================================
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment