0

Let say you have a class, in which you want to store a function to call at a later time. You want this function to be set at construction time. What I'm not sure of, is what is the right way to declare the parameter in the constructor, and whether or not you should use std::move() when storing it. Example:

class FuncCaller
{
public:
   FuncCaller(std::function<int(int)> call_later):m_func(call_later) {}
   inline int DoThings(int i) { return m_func(i); }
private:
   std::function<int(int)> m_func;
}


float b = 5.3;
FuncCaller c([b](int a){ return (int)(a * b); }

or

ComplexNumber cn(1, 4);
FuncCaller another_one([cn&](int a) { return (int)(cn.rational * a); }

My question is, should the constructor take

std::function<int(int)>
or
std::function<int(int)> 
or
std::function<int(int)> &&

Additionally, when capturing the lambda into the member during construction, should I use std::move()

What happens to lambda captures by reference when they are copied?

bpeikes
  • 3,495
  • 9
  • 42
  • 80
  • 1
    *"`std::function` or `std::function` or [...]"* -- What's supposed to be the difference between these two? – JaMiT Sep 02 '23 at 03:01
  • *"What happens to lambda captures by reference when they are copied?"* -- This is a separate question from the main focus of your question. It warrants a separate SO question. – JaMiT Sep 02 '23 at 03:04
  • Does this answer your question? [Pass by value vs pass by rvalue reference](https://stackoverflow.com/questions/37935393/pass-by-value-vs-pass-by-rvalue-reference) – JaMiT Sep 02 '23 at 03:05
  • Does the code you posted compile (it should do)? If so, that's the way to do it. But be careful capturing things by reference. If the original object goes out of scope while the lambda (or a copy of it) is still around then you're in big trouble. – Paul Sanders Sep 02 '23 at 03:34
  • Capturing by reference and then storing will always have the risk of danling references unless by design you can proof the stored lambdas will have a shorter lifetime then referenced objects. – Pepijn Kramer Sep 02 '23 at 06:38

1 Answers1

0

I would write it like that:

public:
   template <typename T>
   FuncCaller(T&& call_later):m_func(std::forward<T>(call_later)) {}

This will construct one std::function object (namely, m_func) which will either copy in or move in the argument as required, no matter what the argument type is.

Comparing this to non-template ctors, this is almost always a bit more efficient. FuncCaller(std::function<int(int)>) always constructs two std::function objects. The other two construct two std::function objects whenever the argument is a lambda or anything other than std::function<int(int)>. When this happens, the actual argument is either moved or copied in as required, and then moved again. In theory a compiler could elide one of the constructors, but in my experiments this doesn't happen. In addition, FuncCaller(std::function<int(int)>&&) will fail to compile when initialising from a std::function<int(int)> variable, which will surprise the user.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243