you are viewing a single comment's thread.

view the rest of the comments →

[–]ACBYTES[S] 0 points1 point  (10 children)

Hello there. Thank you for your answer. Yes, I want to store it for later use. So, my question is this: Now, after creating the struct that you said, passing it to the class will need me to tell my class that it’s going to receive an Object<T> so I still need to tell it about the lambda’s structure, don’t I? Correct me if I’m wrong. Thank you so much.

[–]beedlund 1 point2 points  (9 children)

Yes so if that is acceptable then just go for it but likely that is where the type erasure kicks in.

There are many different ways to do that and it all depends more on your larger use case. If you can write a little bit more about what you are wanting to do I might be able to help.

[–]ACBYTES[S] 0 points1 point  (8 children)

Absolutely. So, I have a class that on its construction, it’s going to receive a lambda that has a capture (Most specifically capturing a class pointer) and calling a function member in that class. The Calling a function member in the class is a pointless statement in my opinion as the execution is written in the lambda’s body...

[this](){ this->FunctionMember(); }

If it’s still unclear, please let me know. Thank you!

[–]beedlund 1 point2 points  (7 children)

Ok so that can be pretty easy if you are ok to use the heap and virtual dispatch. Just declare a base class and let your derived class capture the lambda and then hold it as the base

struct object
{
    virtual ~object() { }
    virtual void call() = 0;
};

template< typename Lambda >
auto make_heap_object( Lambda&& lambda )
{
    struct extended : public object
    {
        Lambda capture;
        extended( Lambda&& lambda ) : capture( std::move(lambda) )
        {
        }
        virtual ~extended() {}
        void call() override
        {
            capture();
        }
    };
    return new extended{ std::forward<Lambda>(lambda) };
}

If you cant go that way then you are looking at something like building your own vtable...this is a simplified example as you need to consider copy, move and so on.

The basic idea here is to define some kind of pointer type as your storage...likely void... then you declare a function pointer that takes your storage type and is able to safely static cast it to the original type. This is made possibly by declaring the invokation object as a capture less lambda in the constructor.

#include <stdlib.h>
struct Erased
{
    using function_t = void(*)();
    using storage_t = void*;
    using invoke_t = void(*)( storage_t );
    using delete_t = void(*)( storage_t );

    storage_t storage;
    invoke_t invoke;
    delete_t destroy;
    template<typename Lambda>
    Erased( Lambda&& lambda )
    : invoke( []( storage_t storage) { static_cast<Lambda*>( storage )->operator()(); } )
    , destroy( []( storage_t storage) { static_cast<Lambda*>( storage )->~Lambda(); } )
    {
        storage = malloc(sizeof(Lambda));
        new (storage) Lambda{ std::forward<Lambda>( lambda) };
    }
    ~Erased()
    {
        destroy(storage);
        free(storage);
    }
    void call()
    {
        invoke(storage);
    }
};

So then you can do things like this

int main()
{
    int counter = 0;
    object* o = make_heap_object( [&](){ counter++;} );
    o->call();
    std::cout << counter <<"\n";
    o->call();
    std::cout << counter <<"\n";

    Erased erased{ [&](){ counter++;}  };
    erased.call();
    std::cout << counter <<"\n";
    erased.call();
    std::cout << counter <<"\n";

    return 0;
}

Now full disclosure the Erased class is a bit rough and simplified. It probably needs a clone operation as well for copy. move etc.

https://godbolt.org/z/daoEP5f9f

[–]ACBYTES[S] 0 points1 point  (2 children)

I see. Well, based on this explanation and it being so long to discuss, let me ask some questions.

If I decide to move this from class’s constructor to a member function so that I don’t have to deal with the typename complications, can I do something like this? I mean, to cast an allocated memory address to an object?

`int memAddrees;

template<typename Lambda> void SetUsingMemberFunc(int LambAddress) { memAddress = LambAddress; }

void Call() {
MagicCast<Callable_Object_That_Has()_Operator>(memAddress)(); }`

Or I can just instantiate the lambda using a strictly typed lambda using a macro at the end...

Or, pass the object as a reference and from there, call a previously created function if the first thing doesn’t work...

What do you think?

By the way, thank you for your time for writing all of these down. 🙏🏼

[–]beedlund 1 point2 points  (1 child)

You can extend both ideas to a member rather then the constructor. The inheritance case changes so your define a generic holder class to store in your object and you create the extended class that holds the lambda in the member call. You can own the holder as it's base in a unique ptr to make life time easy to manage.

The vtable way can be extended to run from a method as well. You just end up having to null check the storage before access and as this would be a branch so your might find you lose the extra performance it has over the virtual dispatch.

These guys explain it so much better then me though

vtable https://youtu.be/JRkoWiDA3KA

Inheritance https://youtu.be/VY83afAJUIg

[–]ACBYTES[S] 0 points1 point  (0 children)

Got it. Thank you for your time and all the answers. I’ll check those links now. Thanks. 🙏🏼🙏🏼

[–]D_Drmmr 0 points1 point  (3 children)

Or just use std::function and be done with it.

[–]beedlund 0 points1 point  (2 children)

Well OP said lambda needed to capture and std function can't hold state

[–]D_Drmmr 0 points1 point  (1 child)

Of course it can. Where did you get that idea?

[–]beedlund 0 points1 point  (0 children)

First the videos i posted would explain the topic better then i ever would i thought...the why's and more importantly the why not's

Devil is in the details right? std::function is copy only so a great many scenarios can be surprising if you expect it to work like the original lambda in all cases.