5

In C++, can I create an implementation of an interface on the fly (which ideally binds local-scope variables.) Not sure how to explain it better, so I will put down what I would like the code to look like (roughly):

// Given the following:

class Visitor
{
    virtual void visit(const Data& data) = 0;
}

class DataStore
{
    void visitData(Visitor& visitor) { /** Invokes visitor with each item of data. */ }
}

// Imagine one would write something like:

void inSomeFunction(DataStore& store)
{
    std::string myName = "bob";

    class MyVisitor: public Visitor
    {
    public:
        MyVisitor(const std::string& p): person(p);
        virtual void visit(const Data& data) override
        {
            std::cout << person << " is visiting " << data << std::endl;
        }
    private:
        std::string person;
    }

    MyVisitor myVisitor(myName);
    store.visitData(myVisitor);
}

// Instead of the above, I want to write instead something like:

void inSomeFunction(DataStore& store)
{
    std::string myName = "bob";

    store.visitData(
        class: public MyVisitor
        {
            virtual void visit(const Data& data) override
            {
                std::cout << myName << " is visiting " << data << std::endl;
            }
        }
    );
}

I realise that I'm asking 2 questions here - can you create an anonymouse class like this, and can you bind variables from the local scope (like I've referred to myName). But I need both for it to be useful.

If the above can't be done, what's the closest one can get to using say C++11 lambdas or similar, in terms of conciseness / lack of boilerplate.

nappyfalcon
  • 909
  • 1
  • 10
  • 17
  • 1
    You can achieve what you want, but not the way you want it. I'd use a functor for something like that, or a visitor that takes a function pointer, then you can just use a lambda. – csl May 24 '16 at 16:53
  • My only issue is that the way you specify the type signature for a function pointer in C++ is horrible. E.g. what would the signature of the visitData method be if I was to use a function pointer instead of an interface? – nappyfalcon May 24 '16 at 16:55
  • Unless there is a new, nicer way of specifying the type for a parameters that takes a function pointer...? – nappyfalcon May 24 '16 at 16:56
  • Style note: Traditional pattern followers would probably call "accept" what you call "visitData". As in, you "accept" a visitor. – Kerrek SB May 24 '16 at 16:58
  • @nappyfalcon But even interfaces will have function signatures. I dunno, I'm just thinking out loud. To me this sounds like some digging into a patterns book. – csl May 24 '16 at 17:09

2 Answers2

10

If you have to have an interface, then you should do what Kerrek suggested.

But even better would be to change your interface from:

class DataStore
{
    void visitData(Visitor& visitor) { 
        // bunch of calls to visitor.visit(...) ... 
    }
}

to:

    template <class Visitor>
    void visitData(Visitor visitor) { 
        // bunch of calls to visitor(...) ... 
    }

This would allow you to simply write:

std::string myName = "bob";
store.visit([myName](const Data& data) {
    std::cout << myName << " is visiting " << data << std::endl;
});

which is far more natural in my opinion. If the interface of DataStore is outside of your control, then this answer is totally moot.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • This looks fantastic...but I don't understand how it works. How does the "[myName](const Data& data) { std::cout << myName << " is visiting " << data << std::endl; }" map to the Visitor param in the visitData method? – nappyfalcon May 24 '16 at 17:25
  • @nappyfalcon - because the C++ _template_ mechanism uses duck typing - in this case the _template parameter type_ `Visitor` is _used_ by calling `operator()` on instances of it ... so _any_ type that implements `operator()` works - and that includes lambdas. The key is that the method `visitData` was changed, by Barry, to a `template method`. – davidbak May 24 '16 at 17:32
6

You can do it with one more level of indirection:

#include <functional>
#include <utility>

class AdHocVisitor : public Visitor
{
public:
    explicit AdHocVisitor(std::function<void(const Data&)> f) : f_(std::move(f)) {}
    void visit(const Data& data) override { f_(data); }
private:
    std::function<void(const Data&)> f_;
};

Usage:

AdHocVisitor v([a, &b, this](const Data&) {});
store.visitData(v);
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084