Arturo Gonzalez
Arturo Gonzalez

Reputation: 345

is it possible to use a lambda instead of a class in c++?

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 );

Upvotes: 2

Views: 445

Answers (2)

Jerry Coffin
Jerry Coffin

Reputation: 490068

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).

Upvotes: 1

catnip
catnip

Reputation: 25388

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.

Upvotes: 3

Related Questions