Skip to content

Instantly share code, notes, and snippets.

@Nexuapex
Last active August 29, 2015 14:00
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 Nexuapex/0ac3ace240c7b5ad1808 to your computer and use it in GitHub Desktop.
Save Nexuapex/0ac3ace240c7b5ad1808 to your computer and use it in GitHub Desktop.
An attempt to reinvent `std::function' that ended up being about things I don't like about C++.
// Because it was possible to implement std::move and std::forward<T> as library
// functions, so that's what happened. Now this header is everywhere. This is
// 2,800 lines of kitchen sink for me.
#include <utility>
// C++ has half-decent pattern matching, but only on types. If only some of this
// energy could be thrown at language constructs like `switch'.
//
// Oh, right, and this relies on partial specialization, which only works on
// classes. So you get these pointless classes that contain a single typedef.
template <typename T>
struct member_function_signature;
// That `const' in there is the first hint of a problem that would show up in
// the real world: the type of a member function includes its qualifiers, and
// I can't parameterize this class with a set of function qualifiers, so you
// technically need to enumerate every possibility (const, volatile, ref-
// qualifiers, and anything added in the future).
template <typename T, typename R, typename... Args>
struct member_function_signature<R (T::*)(Args...) const>
{
typedef R (type)(Args...);
};
// And the whole reason I even have this type alias is that I need to invent a
// way to ask a function object what its signature is, through a circuitous
// route. I can't directly reference the type I want, so I have to name a
// distantly related object and transform it into the type I want (which leads
// to the function qualifiers issue above).
//
// Here we also see another use of the `typename' keyword, to name a qualified
// dependent type. Good luck understanding when or why you need this `typename'
// without getting familiar with the standardese. The problem is that C++
// doesn't really have higher-kinded types, it just has a massive hygenic macro
// system which doesn't mesh well with the context-sensitive grammar.
template <typename T>
using functor_signature = typename member_function_signature<decltype(&T::operator())>::type;
template <typename T>
class function;
// And now to start on the meat of the problem: the fact that C++ doesn't have
// a function type with captures that isn't absurd. `std::function' is less
// efficient than Objective-C's blocks, for heavens' sakes. You know, that
// language where every method call involves a hash table lookup.
template <typename R, typename... Args>
class alignas(16) function<R(Args...)>
{
public:
R operator()(Args... args) const
{
return invoke_(this + 1, args...);
}
// Despite the fact that every pure virtual destructor must have a
// definition, you can't declare it inline because the syntax won't let you.
virtual ~function() = 0;
protected:
// Idiomatic C++ eschews inheritance, and the access specifier `protected'
// is always suspect, but my other choice is awful hackery to reproduce the
// behavior of a virtual destructor. If C++ had better hooks to control the
// way your class behaves when it lives on the stack, this could be avoided.
typedef R (invoker)(void const*, Args...);
// Here I want to complain about constructors being special operators but
// having different syntax than other operators, and the `explicit' keyword
// and opting-in to safe behavior instead of opting-out of it, but at this
// point Stockholm syndrome has kicked in and I kind of like how it works.
explicit function(invoker* invoke)
: invoke_(invoke) {}
function(function const&) = default;
function(function&&) = default;
void operator=(function const&) = delete;
void operator=(function&&) = delete;
// You really do not want to see this function's mangled name or its name in
// a debugger, especially with many arguments. I tried to think of some way
// to pull it out of this class and give it a more reasonable set of type
// parameters, but I got angry and gave up.
template <typename T>
static R do_invoke(void const* functor, Args... args)
{
return (*static_cast<T const*>(functor))(args...);
}
private:
invoker* invoke_;
};
// Here's the utterly trivial out-of-line definition that I shouldn't need.
template <typename R, typename... Args>
function<R(Args...)>::~function() {}
// Here we name the base type once...
template <typename T>
class reified_function
: public function<functor_signature<T>>
{
private:
// ...and then name it again, because there's no way to talk about your
// base's type without fully naming it. Because of multiple inheritance,
// which I don't use. "Don't pay for what you don't use," indeed.
typedef function<functor_signature<T>> base;
static_assert(alignof(T) <= alignof(base), "Cannot capture objects with large alignment requirements");
public:
reified_function() = delete;
// Hey, remember the magical `typename' from earlier? Here's `template',
// used for the same reason: it's impossible to parse anything to do with
// a qualified dependent name without knowing what kind of name it is.
//
// Oh, and there's a bug here. This `T&&' constructor isn't a perfect
// forwarding constructor, because `T' is already determined by this point.
// To know why, you have to understand the reference coalescing rules.
// But that's okay, because only the elite race of "library writers" will
// even attempt this stunt, right?
explicit reified_function(T&& fn)
: base(&base::template do_invoke<T>)
, payload_(std::forward<T>(fn)) {}
private:
T payload_;
};
// A common C++ idiom: functions that exist entirely to work around the lack of
// template argument deduction on classes. So partial specialization only works
// on classes, but argument deduction only works on functions. Isn't that neat?
template <typename T>
reified_function<T> type_erase(T&& fn)
{
return reified_function<T>(std::forward<T>(fn));
}
// And the final kicker: I've used so many features that Visual C++ doesn't yet
// support that I have to assume there would be twice as many whinging comments
// if I tried to write a version of this that I could actually use in real code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment