4

I have a lambda that I need to convert into a callable object so that I can specialize the call operator. My impression has always been that a lambda with an void(auto) signature was equivalent to a callable struct roughly like this:

struct callable {
    Foo & capture;

    template< typename T >
    void operator()( T arg ) { /* ... */ }
}

However, a lambda can access private and protected members when it is declared within a member function.

Here's a simplified example:

#include <iostream>

using namespace std;

class A {
protected:
    void a() { cout << "YES" << endl; }
};

class B : public A {
public:
    void call1();
    void call2();
};


struct callable {
    B * mB;

    void operator()() {
        // This does not compile: 'void A::a()' is protected within this context
        // mB->a();
    }
};

void B::call1() {
    // but then how does this access a() ?!
    [&]() { a(); }();
}

void B::call2() {
    callable c{ this };
    c();
}


int main()
{
    B b;

    b.call1();
    b.call2();
}

Is there any way to emulate that behavior in a callable struct, without declaring it in the header and making it a friend class? That seems problematic because I'm going to have a lot of different callables. I'm also just curious about it, because I was under the impression that lambdas were functionally identical to declaring a struct with a call operator.

Access rights of a lambda capturing this seems to say that the lambda has the same access as a local class. But in my case, I need to emulate a generic lambda, and local classes can't have template member functions.

Ian
  • 2,078
  • 1
  • 17
  • 27
  • 1
    "so that I can specialize the call operator" -- sounds like a bad plan really. Why, and are you willing to reconsider? Overloading is much more cromulant. – Yakk - Adam Nevraumont Sep 12 '17 at 02:55

2 Answers2

2

You can still capture this and &B::a

struct callable {
    B* mB;
    void (A::*m)();

    void operator()() const {
        (mB->*m)();
    }
};

void B::call2() {
    callable c{ this, &B::a };
    c();
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Clever! But--to my surprise--that errors on compile with `'void A::a()' is protected within this context` when instantiating callable. http://coliru.stacked-crooked.com/a/8bc612b89ab1dc5e – Ian Sep 12 '17 at 00:28
  • You have to use `&B::a` instead. – Jarod42 Sep 12 '17 at 00:31
0

You can make the struct callable a friend of B without providing a definition for it. Such a declaration doesn't even make the name visible until the real declaration is encountered:

class B : public A {
  // ...
  friend struct callable;
}

// ERROR: callable isn't visible here:
// callable *foo;

This can be extended to hack in a large number of friends with one declaration if brevity is really important:

class B : public A {
  // ...
  template<int> friend struct callable;
};

// In implementation:
template<>
struct callable<0> { /* ... */ };
template<>
struct callable<1> { /* ... */ };
Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • OP states *"without declaring it in the header and making it a friend class?"*. – Jarod42 Sep 12 '17 at 00:21
  • You don't have to give the full definition of `callable` in the header. It isn't clear to me whether the OP considers a forward declaration to be "declaring it in the header". – Davis Herring Sep 12 '17 at 00:28
  • Yeah, declaring a parent class a friend and inheriting from that one class might be an ok solution, if friend status was inherited. Unfortunately it's not. – Ian Sep 12 '17 at 00:29
  • You can succinctly create many friends with the template hack I added. (This avoids having to have the one friend provide a forwarding function for each member of `A` that you want to use.) – Davis Herring Sep 12 '17 at 01:23