std::function as a friend of the class

Viewed 141

Consider this code :

#include <functional>

class A{
public:
    std::function<void(A* obj)> todo;
    void doWork(){
        if(todo)
            todo(this);
    }

private:
    void dummy(){}
    friend void todo(); // not working
};

int main(int argc, char *argv[])
{
    A tmp;
    tmp.todo = [](A *obj){
        obj->dummy();
    };
    tmp.doWork();

    return 0;
}

Of course, we can't build this code because 'A::dummy': cannot access private member declared in class 'A'. I think it is impossible, but is there a way of accessing to the private members of class A from the lambda declaration ??

2 Answers
friend void todo(); // not working

Yes, it's not working. This declares that the friend function is some function called "todo".

This is not the same thing as a class member of the same name. Friends are functions (or classes), and not class members.

The fundamental issue you are facing here is a combination of two fundamental C++ aspects:

  1. std::function<something> is a concrete, discrete class of its own.

  2. Each lambda is an anonymous class, and all lambda are discrete, different anonymous classes.

You could declare something like:

friend class std::function<void(A* obj)>;

But that's not going to accomplish anything productive. This will allow the std::function template itself to access private members of this class. So, if something in the internal implementation of your C++ library's std::function template needs to access a private class member, it can now do that. But, of course, there's nothing in std::function itself that has any awareness of your class.

And since each lambda itself is a discrete anonymous class, this has no effect on any lambda. std::function itself is a concrete class of its own, which effects type erasure, for these anonymous lambdas.

In short, this cannot be done in C++. What you are really want to have is make a specific anonymous lambda class a friend of this class. But there is no such syntax in C++. You must come up with some other alternate strategy of giving your lambdas access to your class's private members.

Well, you cannot do that directly. Even if you friend the std::function class, it will not friend the lambda itself, which has its own type, distinct from std::function.

What I would do instead is to pass to the lambda something that has access to selected private parts. The nice thing with this idiom is you don't expose more than you need:

class A {
    struct B {
        explicit B(A* s) noexcept : self{s} {}
        void dummy();

    private:
        A* self;
    };

    void dummy();

public:
    std::function<void(B)> todo;

    void doWork() {
        if (todo) todo(B{this});
    }
};

So you pass around a B which has access to everything, but exposes dummy only. That class is private in my example but you can make it public, but this is optional.

You can than implement the todo function like that:

A tmp;

// auto will take the B type, which cannot be named here since it's private.
// Notice we pass by value, since the wrapper contains the pointer.
// Bonus: operator dot works.
// If B would be public, then you could put A::B instead of auto
tmp.todo = [](auto a) {
    a.dummy(); // calls dummy, yay!
    // a.doWork(); // cannot call do work, not exposed by the wrapper.
};
Related