Create a “do-nothing” `std::function` with any sig

2019-06-22 08:11发布

问题:

I would like to create a simple no-op std::function object with an arbitrary signature. To that end, I've created two functions:

template <typename RESULT, typename... ArgsProto>
  std::function<RESULT(ArgsProto...)> GetFuncNoOp()
  {
    // The "default-initialize-and-return" lambda
    return [](ArgsProto...)->RESULT { return {}; };
  }

template <typename... ArgsProto>
  std::function<void(ArgsProto...)> GetFuncNoOp()
  {
    // The "do-nothing" lambda
    return [](ArgsProto...)->void {};
  }

Each of these works well enough (obviously the first version might create uninitialized data members in the RESULT object, but in practice I don't think that would be much of a problem). But the second, void-returning version is necessary because return {}; never returns void (this would be a compile error), and it can't be written as a template-specialization of the first because the initial signature is variadic.

So I am forced to either pick between these two functions and only implement one, or give them different names. But all I really want is to easily initialize std::function objects in such a way that, when called, they do nothing rather than throwing an exception. Is this possible?

Note that the default constructor of std::function does not do what I want.

回答1:

I don't like having to specify the signature.

Assuming you have a std::function implementation where std::function<void()> can accept a function pointer of type int(*)(), this is a non-type erased noop object that can be cast into any std::function:

struct noop {
  struct anything {
    template<class T>
    operator T(){ return {}; }
    // optional reference support.  Somewhat evil.
    template<class T>
    operator T&()const{ static T t{}; return t; }
  };
  template<class...Args>
  anything operator()(Args&&...)const{return {};}
};

if your std::function does not support that conversion, we add:

  template<class...Args>
  operator std::function<void(Args...)>() {
    return [](auto&&...){};
  }

which should handle that case, assuming your std::function is SFINAE friendly.

live example.

To use, just use noop{}. If you really need a function returning a noop, do inline noop GetFuncNoop(){ return{}; }.

A side benefit to this is that if you pass the noop to a non-type erasing operation, we don't get pointless std::function overhead for doing nothing.

The reference support is evil because it creates a global object and propogates references to it all over the place. If one std::function<std::string&()> is called, and the resulting string modified, the modified string is used everywhere (and without any synchronization between uses). Plus allocating global resources without telling anyone seems rude.

I'd just =delete the operator T& case instead, and generate a compile-time error.



回答2:

You are too wedded to braces.

template <typename RESULT, typename... ArgsProto>
std::function<RESULT(ArgsProto...)> GetFuncNoOp()
{
  // && avoids unnecessary copying. Thanks @Yakk
  return [](ArgsProto&&...) { return RESULT(); }; 
}


回答3:

template <class T> struct dummy {
  static auto get() -> T { return {}; }
};
template <> struct dummy<void> {
  static auto get() -> void {}
};

template <typename RESULT, typename... ArgsProto>
std::function<RESULT(ArgsProto...)> GetFuncNoOp()
{
  return [](ArgsProto...)->RESULT { return dummy<RESULT>::get(); };
}

but... @T.C. solution is so much more elegant. Just wanted to show another way that can be applied anywhere you need to specialize just a "part" of something.