10

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.

Community
  • 1
  • 1
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167

3 Answers3

11

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(); }; 
}
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 2
    Oh, I didn't realize that `void()` could be "initialized" in that way. What's actually going on there? Is it any different from `return;` ? – Kyle Strand Jul 07 '15 at 17:03
  • @KyleStrand Nothing different. `void()` is a prvalue of type `void`, that's all. – T.C. Jul 07 '15 at 17:05
  • ....and I notice that `RESULT{}` does not behave the same. Yet another issue with "univeral" initialization syntax? – Kyle Strand Jul 07 '15 at 17:11
  • @KyleStrand Yes. `void()` is fine; `void{}` isn't. No idea why they made it that way. – T.C. Jul 07 '15 at 17:12
6

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Huh. Specifying the signature is indeed a pretty bad annoyance with T.C.'s correction of my solution, so I do like this better, but I don't quite understand it. Why does `anything::() {return {}; }` (i.e. the specialization of `anything::()` to `void`) not cause a compile error, since `return {};` can't be used to return `void`? – Kyle Strand Jul 07 '15 at 17:46
  • Also, would you mind putting the `reference-returning` version back in your answer, with a brief explanation of why you think it's "evil" (or dangerous or whatever)? – Kyle Strand Jul 07 '15 at 17:46
  • @KyleStrand `std::function` never tries to convert the return value of the passed in object to `void` in modern implementations. So the returned `anything` is just discarded. Some of the evil of the reference-returning `anything` is now added, and I made it work (original version didn't work, required some finessing of `const` to make it work right). `noop` **always** returns an `anything`. The `anything` can be converted to anything, which is what lets it match any `std::function` signature's return value requirements. – Yakk - Adam Nevraumont Jul 07 '15 at 17:50
  • Awesome. I'm changing `operator T&()` to return `T const&` (and changing `static T t` to `static const T t`), which provides *some* safety, and for a do-nothing function, that's probably good enough. – Kyle Strand Jul 07 '15 at 17:55
  • ....how is the `anything::()` overload even disambiguated, though? `noop::()` never returns a `const`, yet everything appears to work as expected (though I expect the actual print operation is undefined behavior): http://coliru.stacked-crooked.com/a/5831256cb7e60d22 – Kyle Strand Jul 07 '15 at 17:58
  • @KyleStrand First, make the `operator T()` non-const. That makes it preferred, which disambiguates in the `int x = noop::anything{};` case. Second, `int const& y = noop::anything{};` prefers the `operator T const&()const` over `operator T()` despite the failure for the type of `this` to match, at least in practice. (by working directly with `noop::anything{}` you get much better error messages). I'd have to do some work to find standard quotes proving that this is correct. There is no undefined behavior as far as I can tell, all the references are grounded in function-local `static` objects. – Yakk - Adam Nevraumont Jul 07 '15 at 18:18
  • `static const T t;` can be undefined depending on the type of `T`; if `T` has no user-defined initializer, I don't think Clang will even compile that code: http://clang.llvm.org/compatibility.html#default_init_const (I've changed this to `static const T t{}`, which ensures some kind of initialization.) – Kyle Strand Jul 07 '15 at 18:30
  • Indeed, leaving off the `{}` causes an error on clang when using a POD class as a return type. But there's another error with Clang++ that I don't quite understand, when trying to instantiate a `noop` that takes a `std::string` as an argument: http://coliru.stacked-crooked.com/ – Kyle Strand Jul 07 '15 at 18:34
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/82639/discussion-between-kyle-strand-and-yakk). – Kyle Strand Jul 07 '15 at 18:38
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.

bolov
  • 72,283
  • 15
  • 145
  • 224