4

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

  • struct with_prop is corresponding to struct foo.
  • struct table is corresponding to struct bar.
  • The outer lambda expression [](with_prop& p) {... is corresponding to void bar::memfun(foo& f).
    • Due to SML overload resolution, the parameter foo& f cannot be auto& f.
  • The inner lambda expression [](auto& p) -> void { ... is corresponding to [](auto& f) -> void { ...
  • auto table::operator()() const noexcept cannot separate to declaration and definition because SML uses the return type before the operator()() is defined.
Takatoshi Kondo
  • 3,111
  • 17
  • 36
  • Can you convert the intervening text to comments and put the code together? This makes it clearer what is intended to be in the [mre]. – L. F. Oct 05 '19 at 07:03
  • You use an incomplete struct, which is just a declaration without any member function declaration. So the compiler doesn't know at this point what is `func` which is member function to the struct. So, why not define the struct `foo` before struct `bar` ?? Or define struct `foo` by the same way you defined the lambda struct at your first try ? – user9335240 Oct 05 '19 at 07:14
  • @user9335240, this code is focused on the compiler behavior. The original issue is https://github.com/boost-experimental/sml/issues/93#issuecomment-283629397. Boost.SML requires `auto` return type transition table. In this issue, `struct table` cannot be separable due to Boost.SML requirement. – Takatoshi Kondo Oct 05 '19 at 07:19
  • _`// Write explicit return type seems to instanciate lambda body lazily on g++`_ "instanciate" like in "template instanciate" or like in "struct instanciate"? Whenever the lambda struct is instanciated doesn't play a role IMHO. "Making" functions happens at compile time. Hence, I find the "incomplete struct" reasonable. If `g++` happens to compile this it should have something to do with whether things happen in [Phase 7 or Phase 8](https://en.cppreference.com/w/cpp/language/translation_phases). I wonder if you could count on `g++`s behavior in this case. – Scheff's Cat Oct 05 '19 at 07:47
  • @TakatoshiKondo your code seems this function is a semi-pure function (not a closure) of `foo` (and in the issue, it is `with_prop`), so why not use function pointers here ?? `typedef void (*lambda_type) (struct foo &);` then declare an `extern const`, then declare it as a lambda after `foo` declaration. https://wandbox.org/permlink/6EhybAMZjfMgZ2v5 – user9335240 Oct 05 '19 at 08:02
  • @user9335240, at sml example, `table::operator()` return type must not be `auto`. It is meta programming trick. So I cannot define the operator() as the (member) function pointer. SML is supported on wandbox. This is the code that compiles on g++ but not compile on clang++ https://wandbox.org/permlink/Zs20FOQLWozErp3O . I think that it cannot be re-write using (member) function pointer. – Takatoshi Kondo Oct 05 '19 at 08:10
  • @TakatoshiKondo https://wandbox.org/permlink/PlcT9JSUJsFZebNt ?? – user9335240 Oct 05 '19 at 08:20
  • @user9335240, thank you for your Boost.SML code. It solves the issue. I didn't know that way. Good to know. Thanks again! I still looking for a way to lambda expression (with auto parameter) lazily if it is possible. I updated the question. – Takatoshi Kondo Oct 05 '19 at 08:30
  • @Scheff, thank you for the comment. I updated the question. See the last part. It is truly equivalent code to the lambda expression. `void lambda::operator()(T&)` instantiate lazily both g++ and clang++. It doesn't separate definition and declaration. I expects that I can do the same thing using lambda expression . – Takatoshi Kondo Oct 05 '19 at 08:32

2 Answers2

0

It seems to me that what you're looking for (and that you're almost using in your last two approaches) is a generic lambda.

I mean...

#include <iostream>

// struct foo; // forward declaration (not needed at all)

auto bar = [](auto & f) { f.func(); };

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

int main() {
    foo f;
    bar(f);
}

The trick is receiving, in the lambda, a generic type (auto, that is equivalent at your template lambda::operator() in your last approach) that uses a func().

This way, the compiler doesn't need anymore, at the moment of the bar lambda definition, to know how foo::func() is made.

Observe that also your second approach is based over this solution, just overcomplicated.

-- EDIT --

The OP precise

I cannot replace foo& with auto&. I should add my question background. So I added Background to my question at the last part.

Sorry but, also reading your background edit, I don't understand your exact needs.

Anyway, if the point is that you need a lambda that accept a foo&, I propose the following solution that write it in a template function but postpone it's production after foo definition.

Observe the final static_assert() that verify that bar is a lambda that accept a foo& (better: verify that is convertible to a function pointer that accept a foo& and return void)

#include <iostream>

// struct foo; // no forward declaration needed

template <typename T>
auto baz ()
 { return [](T & f){ f.func(); }; }

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

int main() {
    foo f;

    auto bar = baz<foo>();

    bar(f);

    static_assert( std::is_same_v<decltype(+bar), void(*)(foo &)>, "!" );
}
max66
  • 65,235
  • 10
  • 71
  • 111
  • @user9335240 - works with both, in my linux platform (g++ 8.3.0 and clang++ 7.0.1) – max66 Oct 05 '19 at 09:22
  • @max66, thank you for the answer. I cannot replace `foo&` with `auto&`. I should add my question background. So I added `Background` to my question at the last part. – Takatoshi Kondo Oct 05 '19 at 10:44
  • @TakatoshiKondo - Sorry but I don't understand your exact needs. I've added to my answer another example where `bar` is a lambda that accept a `foo&`. Hope this helps. – max66 Oct 05 '19 at 11:50
  • @max66, https://wandbox.org/permlink/qX1W0cCYrsXv1phD is what I want to do. It compiles on g++ (I think it is one of UB), but doesn't compile clang++. I'm looking for the way to be well-formed. If I can do, I don't want to separate things. – Takatoshi Kondo Oct 05 '19 at 11:54
0

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Do you mean my second approach **function object with member function template approach** is ill-formed and looks working well by accident, it is undefined behavior? Is that right? I added question background. https://wandbox.org/permlink/Z2gUxZOA5B2is2j7 is not separated one and https://wandbox.org/permlink/fzHXGIVZ4zBcorAm is separated one. But separated one causes compile error because SML require the operator()() return type before its definition. I'm afraid that the library design might have a problem... – Takatoshi Kondo Oct 05 '19 at 10:59
  • @TakatoshiKondo - Yes, both the template and the generic lambda versions you've shown are ill-formed NDR. As for SML, I can't speak to its design. I'm unfamiliar with it. – StoryTeller - Unslander Monica Oct 05 '19 at 11:24
  • thank you for clarify. Is my first approach **Separate declaration and definition approach** well formed? I guess so. – Takatoshi Kondo Oct 05 '19 at 11:30
  • @TakatoshiKondo - Yes, it's well-formed. Lack of a template also allows the compiler to enforce it. – StoryTeller - Unslander Monica Oct 05 '19 at 12:21
  • I considered your answer. I don't think that the reason of my code is ill-formed is ODR violation. I think that the last sentence you emphasized is for multiple translation units. It is natural case to describe about ODR. Only in one translation unit, the point of instantiation should be decided to one point. What do you think? – Takatoshi Kondo Oct 07 '19 at 06:57
  • @TakatoshiKondo - It's in the same paragraph the describes multiple points of instantiation in a *single* TU. Then it says that any two points of instantiation (which IMO includes two in the same TU) must agree. So I stand by what I previously said. – StoryTeller - Unslander Monica Oct 07 '19 at 07:00
  • Hmm it seems that you are right. It is counterintuitive for me but I need to convince myself. If the standard said that the first point of instantiation is delayed to the second (last) point of instantiation on some condition, it could be well-formed but I couldn't find such description. Just clarify the simplified version https://wandbox.org/permlink/QES2dz8QnMUOonyL is the same result (ill-formed) ? I guess so. – Takatoshi Kondo Oct 07 '19 at 23:08
  • @TakatoshiKondo - Yes, the same result, ill-formed. – StoryTeller - Unslander Monica Oct 08 '19 at 03:49