Last active
August 29, 2015 14:00
-
-
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++.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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