2

I have recently been reading a design patterns book written for Java, but I am actually interested in applying the techniques to C++, so I am "translating" the examples from Java to C++. I found out that in Java, it is possible to use a lambda expression as a parameter of a function instead of an object (instance of a class). With my current C++ knowledge, I have not found a way to do something equivalent or similar using C++, I don't even know if that is possible, so I give it a try here.

In Java:

public class Light { public void on() { //whatever } public void off() { //whatever } } public interface Command { public virtual void execute(); }; public class RemoteControl { Command m_command; public RemoteControl() {} public void setCommand(Command command) { m_command = command; } } 

According to the book, in Java, given the characteristics of the interface (just one method), it is possible to use a lambda with the signature of the method declared in the interface instead of an "instance" of the interface, eg:

RemoteControl remoteControl; Light livingRoomLight; remoteControl.setCommand( ()->{livingRoomLight.on();} ); 

What can I do in C++ to achieve something similar?

My current C++ code:

class Light { public: Light() {} void on() { //whatever } void off() { //whatever } }; class Command { public: virtual ~Command() = default; virtual void execute() = 0; }; class RemoteControl { private: std::shared_ptr<Command> m_command = {nullptr}; public: RemoteControl() {} void setCommand(std::shared_ptr<Command> command) { m_command = command; } }; 

Is it possible in C++ to use a lambda expression instead of a pointer to a Command instance?

Something like:

std::unique_ptr<RemoteControl> remoteControl = std::make_unique<RemoteControl>(); std::shared_ptr<Light> livingRoomLight = std::make_shared<Light>(); remoteControl->setCommand( [livingRoomLight](){ livingRoomLight->on(); ); 

or

RemoteControl* remoteControl = new RemoteControl; Light* livingRoomLight = new Light; std::function<void()> livingRoomLightOn = [livingRoomLight]() { livingRoomLight->on(); }; remoteControl->setCommand( &livingRoomLightOn ); 

2 Answers 2

3

It's possible, but your syntax is wrong. Here's an example of how to do it:

#include <iostream> #include <functional> class Light { public: void on () { std::cout << "Light on\n"; } void off () { std::cout << "Light of\n"; } }; class RemoteControl { public: void setCommand (std::function <void ()> command) { m_command = command; } void performCommand () { m_command (); } private: std::function <void ()> m_command; }; int main () { Light livingRoomLight; RemoteControl remoteControl; remoteControl.setCommand ([&livingRoomLight] () { livingRoomLight.on (); }); remoteControl.performCommand (); } 

Output:

Light on 

Note that livingRoomLight is passed to the lambda by reference so that it can modify the original object.

Sign up to request clarification or add additional context in comments.

7 Comments

For this particular case, the living room light should be passed by reference.
@MichaëlRoy Good point, I will edit my answer.
Using an std::function as a parameter is expensive, and should generally be discouraged. The parameter should be a templated, and an std::function used only for storing the closure (there are a few cases where this adds enough difficulty to justify using an std::function, but that doesn't seem to be the case here).
@JerryCoffin I don't see the value in using a template here. Surely the whole point is that you can store different commands in remoteControl at different times, and a template can't do that.
@PaulSanders: I don't think it's at all obvious that that would be the intent at all. And even assuming that is what's desired, passing the parameter as a template type certainly doesn't prevent doing that.
|
1

Yes, you can pass a lambda1, but since each lambda expression creates a new, unique type (and you don't know the name of that type) you have2 to use a template parameter for the type of the lambda:

template <class Command> class RemoteControl { std::function<void()> command; public: RemoteControl(Command const &command) : command(command) {} void operator() { command(); } }; int main() { Light light; RemoteControl turnOn([&] mutable { light.on(); }); RemoteControl turnOff([&] mutable { light.off(); }); // ... turnOn(); // invoke the remote control to turn on the light // ... turnOff(); // invoke the other remote control to turn off the light } 

I would urge that this be used with caution though. In particular, you're basically using a rather complex, roundabout way of creating something equivalent to just having the Light as a simple variable, with a couple of free functions to manipulate it:

bool light; // probably not the real type, but it'll do for now void turnOn() { light = true; } void turnOff() { light = false; } 

Java doesn't allow free functions, so you have to use this roundabout way to imitate them. C++ doesn't restrict you that way, so if you have control of the code that's currently embodied in the Light class, and you want it to act like a simple variable that's manipulated by free functions, then you should probably do that directly.


  1. Well, technically, what you're passing is a "closure". A "lambda expression" basically refers to syntax in the source code. The "thing" that's created by the lambda expression is the closure--but unless you're exceptionally anal, don't worry about it--most people would use "lambda" exactly like you did.
  2. Well, technically, you don't have to. You can use an std::function instead--but as a rule, you should use a template type for the parameter itself. std::function should be used only for storing the lambda (if you need to do that).

9 Comments

You don't HAVE to use a template.... std::function<> works fine as well. The problem with templates, is that a new function is generated for each call to the template function, this contributes to code bloat. On the other hand, using std::function MAY prevent the compiler to inline small functions properlyk, resulting in performance loss. As with any engineering choice, there are trade-offs one needs to be aware of, before choosing a solution.
@MichaëlRoy: No, you don't have to (thus my second foot note, specifically pointing out that it isn't an absolute requirement). As for "code bloat"--feel free to show a demonstration of its happening. People have been claiming this for a decade now, but I've yet to see anybody demonstrate it (and it hasn't happened in my testing either).
For code bloat, all you need to do is check the output assembly code. The amount of bloat will depend on the amount and complexity of code involved, size of the function, size of lambdas, and number of calls with different lambdas.
@MichaëlRoy: Not true. You have to look at the code after it's been linked (linkers do more than you realize). This problem first arose when templates were added, and people noticed that (for example) vector<int> and vector<unsigned> produced separate (but identical) sequences of code for essentially all operations. By the late 1990's, they'd rewritten linkers so those separate sequences got merged together. The same works fine for lambdas as well. The problem you're claiming to happen from lambdas had actually been fixed 12-15 years before C++ had lambdas at all.
Read resulting compiled assembly code. Each template instantiation results in a different function. As soon as the code gets complex enough, the compiler cannot inline the tempate instantiations and will generate a different copy for each instantiation. That's a secret to no one.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.