3
\$\begingroup\$

An alternative to std::function for when compile time is more important than runtime performance. Doesn't pull in any headers.

(#include <functional> pulls in 19k lines of code.)

This satisfies my needs, but I'm curious how it could be improved (without adding any dependencies).

template<typename>
class func;

// Alternative to std::function for when compile time is
// more important than runtime performance. Doesn't pull in
// any headers.
template<typename Result, typename... Arguments>
class func<Result (Arguments...)> {

  struct holder {
    virtual ~holder() {}
    virtual Result call(Arguments...) = 0;
    virtual holder* clone() = 0;
  };

  template<typename Lambda>
  struct lambda_holder : public holder {
    virtual ~lambda_holder() {}
    Lambda lambda;
    lambda_holder(Lambda l) : lambda(l) { }
    Result call(Arguments... args) override {
      return lambda(args...);
    }
    holder* clone() override {
      return new lambda_holder<Lambda>(lambda);
    }
  };

public:

  func() { }

  func(const func& f) {
    holder = f.holder->clone();
  }

  func& operator=(const func& f) {
    if(holder) { delete holder; }
    holder = f.holder ? f.holder->clone() : nullptr;
    return *this;
  }

  // Create from a lambda.
  template<class F>
  func(F f) {
    holder = new lambda_holder<F>{f};
  }

  ~func() {
    if(holder) { delete holder; }
  }

  Result operator()(Arguments... args) const {
    assert(holder);
    return holder->call(args...);
  }

  operator bool() const {
    return holder;
  }

private:

  holder *holder = nullptr;

};

\$\endgroup\$
  • \$\begingroup\$ Did you aim to have the same semantics as std::function? \$\endgroup\$ – L. F. Jul 11 at 0:53
  • \$\begingroup\$ @L.F. Not really. I'm not even familiar with the finer points. \$\endgroup\$ – Taylor Jul 11 at 3:45
3
\$\begingroup\$
  1. Self-assignment may be expensive, but it should be a no-op instead of UB. I suggest copy-and-swap.

  2. Omitting move-semantics will most certainly cost you.

  3. Your naming of the internal class suggests all callables are lambdas. Not true!

  4. You cannot store any move-only callables. Admittedly std::function is also crippled in that respect. Just throw an exception if the non-copyable object needs to be copied.

  5. If your function has return-type void, it should simply discard the callables return-value. You don't.

  6. std::function throws an exception if empty when called. Using assert() is less than a pale imitation.

  7. delete p; if p is a null pointer is a no-op. No need to double-check.

  8. Conversion to bool should be explicit. Otherwise, conversion to any arithmetic type can happen.

  9. As you said, yours is less efficient. That's especially the case when std::function benefits from mandatory small-object-optimization.

  10. You don't use SFINAE when creating from a callable. That can be inconvenient.

\$\endgroup\$
  • \$\begingroup\$ std::function can't do (4) either, right? \$\endgroup\$ – Taylor Jul 11 at 1:49
  • 1
    \$\begingroup\$ @Taylor I don't think it can. But that hole is easily filled by deferring the error to runtime (throw exception when attempting to copy). \$\endgroup\$ – Deduplicator Jul 11 at 2:37
  • \$\begingroup\$ I don't care much about performance of copying the function, but could I get similar calling performance as std::function without too much effort? \$\endgroup\$ – Taylor Jul 11 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.