3

I'm trying to declare a class "Lambdas" that would provide lambdas (and their type information) to another class "Test". Lambdas also is holding the "this" reference to concrete Test instance for access of Test public members, inside the lambdas. I do it to define the lambdas once and then to deduce types anywhere else through decltype() But I get error: Member access to incomplete type:

template <typename T>
struct LambdasInstances {
    T * self;
    explicit LambdasInstances(T * p) : self(p) {} // CAPTURE Test "this"

    auto genLambda1() {
        return [=](int x){
            self->testVar; // ERROR: Member access to incomplete type
        };
    }
};

class Test3 {
public:
    LambdasInstances<Test3> instances;
    int testVar;

    Test3() : instances(this) {}

    decltype(instances.genLambda1()) varLambda = instances.genLambda1();

    void useLambda() { varLambda(123); }
};

BUT IF I would make genLambda() externally defined, then I would run to another problem - ERROR: genLambda() with deduced type cannot be used before its defined!:

template <typename T>
struct LambdasInstances {
    T * self;
    explicit LambdasInstances(T * p) : self(p) {}
    auto genLambda1(); // would be defined after Test3 declaration
};


class Test3 {
public:
    int testVar;
    LambdasInstances<Test3> instances;
    Test3() : instances(this) {}
    decltype(instances.genLambda1()) varLambda = instances.genLambda1();
};

// IF WE DEFINE AFTER :: ^ genLambda() with deduced type cannot be used before its defined!
template< typename T>
auto LambdasInstances<T>::genLambda1() {
    return [=](int x){
        self->testVar;
    };
}
barney
  • 2,172
  • 1
  • 16
  • 25
  • Do you really need the `instances` member? Given that you also have the returned lambda as member. – Daniel Jour Jun 03 '17 at 16:12
  • @DanielJour The `instances` member is here to incapsulate the Test3's `this` inside the `LambdasInstances`. I don't know how else can I inject `this` to my lambdas. According to `varLambda` is the member because I want later use `Test3` as a state machine so I need a class-visible name to refer to `currentState = varLambda;` – barney Jun 03 '17 at 16:21
  • Idk if this helps (or is even valid), but apparently this works: http://coliru.stacked-crooked.com/a/b63cbf45576c4bf4 – Weak to Enuma Elish Jun 03 '17 at 16:29
  • @JamesRoot thanks, this is definitely an interesting way to explicitly access the specific variable of `class` with the class's member pointer. Wonder if its possible to make it implicit somehow... – barney Jun 03 '17 at 16:35
  • Does this solve your problem? https://pastebin.com/3sXkGY5F – smoothware Jun 03 '17 at 18:47
  • @smoothware Thanks! Unfortunatelly no, as I need to have full access to the Test class public interface (all methods and ivars) from inside the lambda. – barney Jun 03 '17 at 20:06

1 Answers1

1

The compiler might require the definition of the whole type available to be able to know the offset of the members (e.g. in the expression self->testVar, the compiler has to know the offset of testVar), but it might not be able to know the offset of a particular member until it will get the whole definition, because the compiler must know the alignment of your structure/class (I would even guess that some not straight forward logic might be involved when computing the padding between members) that comes after the knowledge of all members, see this, that is totally compiler and platform specific.

So back to your problem. You tell the compiler to define Test3 with genLambda1 as a member, that is a lambda that has to know the offset of member testVar. Seems easy, right? But the offset of testVar depends on the definition of the whole Test3 (see the paragraph above) - here we are in the loop.

You would say: "Hey stupid compiler, I give only a pointer to the lambda, not a copy by value where you have to know the whole size of the `Test3, why would you stop me from doing that?". Quite a legit question because theoretically compiler can resolve offset later, but seems like the compilers are not smart enough. And the standard says:

The lambda-expression’s compound-statement yields the function-body (8.4) of the function call operator ...

That basically says that the lambda body is the function body, but in the function body you cannot have incomplete types right? Lambdas are relatively new to C++ and not all corner cases are elaborated, so let's hope that in some future this will be resolved, of course the compilers will be more complicated as well as the standard.

For you problem I see the following resolution:

template <typename T>
struct LambdasInstances {
  explicit LambdasInstances(T* p) : _lambda([=](int x) { return p->testVar; }) {}

  auto genLambda1() { return _lambda; }

private:
  std::function<void(int)> _lambda;
};

class Test3 {
public:
  int testVar;
  LambdasInstances<Test3> instances;

  Test3() : instances(this) {}

  decltype(instances.genLambda1()) varLambda = instances.genLambda1();
};

int main() {
  Test3 test3;
  Test3* test3_ptr;
  LambdasInstances<Test3> instances(&test3);
  auto lambda = [=](int x) { return test3_ptr->testVar; };
  std::function<void(int)> functor = lambda;
  cerr << sizeof(Test3) << endl;
  cerr << sizeof(LambdasInstances<Test3>) << endl;
  cerr << sizeof(lambda) << endl;
  cerr << sizeof(functor) << endl;
  return 0;
}

The difference is that std::function gives you a level of abstraction that protects the type LambdasInstances::genLambda1 from the definition of Test3. Unfortunately, as you will see from the main output the function takes more memory than the lambda. If this does not satisfy your needs I recommend to revise the design and may be you will find something in the old good techniques before the era of lambdas.

Yuki
  • 3,857
  • 5
  • 25
  • 43
  • Thanks for your comment. If I go with `std::function` I need to specify the type information twice: in `std::function`'s template parameter `` and while defining the lambda itself. So I just wanted to build a scheme while I could freely add function providing any lambdas I want. You can argue that `decltype` is ugly as well and I agree. I wish I could use `auto` in class member somehow... Also std::function also requires virtual function call & heap alloc. I could always have it in the `Test` class directly and assign from `Lambdas` by class-member initialization syntax – barney Jun 04 '17 at 17:13