c++lambdac++14lazy-evaluationgeneric-lambda

lazy instantiation for lambda expression


I want to access foo::func() in the lambda expression but the class foo is declared but not defined at this point. Is there any way to lambda expression lazily ?

If I replace the lambda expression with the equivalent function object, then I can do that.

Here is the equivalent code:

Separate declaration and definition approach

struct foo; // forward declaration

struct lambda {
    void operator()(foo& f); // `lambda` only has declaration of `operator()`.
};

struct bar {
    void memfun(foo& f) {
        // Call `lambda` function object with the reference of incomplete `foo`.
        lambda()(f);
    }
};

struct foo { // Define foo
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

// Define `lambda::operator()` after definition of `foo`.
inline void lambda::operator()(foo& f) {
    f.func();
}

int main() {
    foo f;
    bar b;
    b.memfun(f);
}

Running demo: https://wandbox.org/permlink/12xV6655DZXZxLqF

It can be compiled on both g++ and clang++.

Lambda expression approach that is my goal

I tried to eliminate struct lambda.

Here is the code:

struct foo; // forward declaration

struct bar {
    void memfun(foo& f) {
        // Write explicit return type seems to instanciate 
        // lambda body lazily on g++ 
        [](auto& f) -> void {
            f.func();
        }(f);
    }
};

struct foo { // definition
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};


int main() {
    foo f;
    bar b;
    b.memfun(f);
}

The point is writing return type void explicitly. If I omit this, then the compiler both g++ and clang++ output error "ember access into incomplete type 'foo'" at f.func();. If I add void return type, it seems that g++ instantiate the body of the lambda expression lazily. However clang++ still outputs the same error.

Result:

Which compiler is valid?

If clang++ is valid, is there any way to instantiate the body of the lambda expression lazily similar to the equivalent struct lambda ?

function object with member function template approach

I noticed that the Separate declaration and definition approach is not truly equivalent to the Lambda expression approach. The parameter of the lambda expression is auto& but the parameter of lambda::operation() of the Separate declaration and definition approach is foo&.

It should be template. This is the equivalent code:

struct foo; // forward declaration

struct lambda {
    template <typename T>
    void operator()(T& f) {
        f.func();
    }
};

struct bar {
    void memfun(foo& f) {
        lambda()(f);
    }
};

struct foo { // definition
    void func() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main() {
    foo f;
    bar b;
    b.memfun(f);
}

Running Demo: https://wandbox.org/permlink/dJ1tqQE8dIMNZqgY

It doesn't require separate decralation of lambda::operator(). And instantiate it lazily on both g++ and clang++. I'm looking for a way to the same thing using lambda expression if it is possible.

Background (Why do I need this?)

I'm using Boost (Candidate) SML, state machine library based on meta programming.

See https://github.com/boost-experimental/sml/issues/93#issuecomment-283630876


Solution

  • A bit tangential, but people should be made aware. The code with the template "working" relies on undefined behavior I'm afraid, on account of being ill-formed NDR. I's brittle and could easily break.

    [temp.point] (emphasis mine)

    1 For a function template specialization, a member function template specialization, or a specialization for a member function or static data member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization and the context from which it is referenced depends on a template parameter, the point of instantiation of the specialization is the point of instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.

    8 A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.

    So first it means that the operator() template has two points of instantiation. One is right after bar, and the other is at the end of the translation unit. In the first point of instantiation foo is incomplete, while at the second it is complete.

    At those two points of instantiation, the template specialization has a different meaning! In one, the instantiated specialization is ill-formed, because it calls a member function of an incomplete type. While in the second the type is completed. Like the last sentence of the quote says, this is ill-formed NDR.

    The only way to make it well-formed is to shift the code around a bit.

    struct bar {
        void memfun(foo& f);
    };
    
    struct foo { // definition
        void func() {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
    };
    
    void bar::memfun(foo& f) {
        [](auto& f) -> void {
            f.func();
        }(f);
    }
    

    Now the two points of instantiation agree on their meaning, and the risk of nasal demons is gone.