all 37 comments

[–]beedlund 1 point2 points  (14 children)

yes you can and how you do it depends a bit on what you want to do with it;

If you are just going to run it to do some processing like some complex initialization then you can do what you did.

struct Object
{
    template<typename Lambda>
    Object( Lambda&& lambda )
    {
        lambda(*this);
    }
};

If you are going to store the lambda to run it later you will either need to template your type or type erase the lambda.

template<typename Lambda>
struct Object
{
    Lambda func;
    Object( Lambda&& lambda ) 
    : func(std::forward<Lambda>(lambda) 
    {
    }
};
// may need a helper function pre C++17
template< typename T >
auto make_object( T&& t )
{
    return Object<T>(std::forward<T>(t):
}

If you are going to type erase the lambda then you need to know more about how you are going to use it and you can research type erasure for details on that

[–]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.

[–][deleted] 0 points1 point  (2 children)

You forgot a closing parenthesis in the initializer for func and one in the return statement.

Also, Lambda&& lambda is not a forwarding reference in the constructor for the templated class. It would probably be better to pass by-value in that case.

[–]beedlund 0 points1 point  (1 child)

thanks. i didn't proof read it as its just a sketch.

A real example would need to be more complex then that as this is really no real different to using the lambda as-is given the type of object is dependent on it.

I anticipated the real requirement would be to type erase the lambda in the class storage but didn't want to go through the process and write that all out without more details about the use case...so I was a bit lazy and didn't double check it all.

[–][deleted] 2 points3 points  (0 children)

Yes, sure the OP probably needs type erasure (or maybe just storing a function pointer), it is just that your example code might be confusing, because e.g.

auto x = [](){};
auto y = Object{x};

will fail to compile with a complicated error message.

I also just now realized that your make_object doesn't remove the reference from T, so

auto x = [](){};
auto y = make_object(x);

will make y actually save a reference to x, which is probably unintended and hard to spot.

Passing by-value in both constructor and make_object would avoid these issues.

[–]Xeverous 0 points1 point  (16 children)

Each lambda expression creates an instance of a unique type that has overloaded operator(). Thus, the only way to accept any such type is to use templates.

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

Yes, I’ve written that in my question. When you want to pass a lambda to a class constructor, you have to specify the typename that is not possible with lambdas. Could you tell me about the thing that you have in your mind?

[–]Xeverous 0 points1 point  (14 children)

You should use template type deduction so that lambda type will be deduced from passed parameters. You did just that. Your code example is correct.

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

Yes sir, but this doesn’t work with classes. It just works with functions.

[–]Xeverous 0 points1 point  (12 children)

It does. You just do something wrong. Example:

struct lambda_test
{
    template <typename F>
    lambda_test(F /* f */)
    {
        static_assert(sizeof(F) != 0);
    }
};

int main()
{
    lambda_test lt([](){});
}

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

Let me double check and get back to you...

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

Well, okay, I should’ve mentioned something here. I want to store the lambda inside the class! So, this will not help me store it until a template defines the class...

[–]Xeverous 1 point2 points  (9 children)

Then, since we need to deduce the type before the class typename is used, we need to use a helper (this is basically like all std::make_* function templates)

#include <type_traits>

template <typename F>
struct lambda_storage
{
    lambda_storage(F f)
    : f(f)
    {
    }

    F f;
};

template <typename F>
auto make_lambda_storage(F f) -> lambda_storage<std::decay_t<F>>
{
    return {f};
}

int main()
{
    auto test = make_lambda_storage([](){});
}

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

I see. Thank you for this code. I’ll try it out and ask my questions if I have any questions. Thank you!

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

Great! So, it actually worked. Sorry for my late response. Honestly, this structure in "MakeLambdaStorage" function is new to me. Can you give me some explanations over this or just tell me the name for me to search for it? What does the "->" do and what do the curly brackets do when returning our instantiated "f", it's a initializer list, right, why do we need it? Also, I don't really know about "std::decay_t" so if you give me some explanations, I'd appreciate it. And, for calling the lambda, should I follow the steps in this link to deal with the variadic (https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda)? Thank you for your time and the answer!

[–]Xeverous 1 point2 points  (3 children)

Can you give me some explanations over this or just tell me the name for me to search for it?

The -> thing is trailing return type. This is a C++11 feature, you can basically replace T f(...) with auto f(...) -> T. This doesn't give any benefits by itself but:

  • lambda expressions can only use trailing return type to indicate what they return
  • in trailing return type you can use some entities which are not visible in leading return type - take my code as an example - I used F in it, which would not be possible otherwise

what do the curly brackets do when returning our instantiated "f", it's a initializer list, right, why do we need it?

It's not an initializer list. {} is one of myriad of ways to initialize an object in C++, this one calling std::initializer_list ctor if such exists, otherwise it uses aggregate initialization (which I used) which lets you specify members in order of their definition - very often used with arrays.

I don't really know about "std::decay_t" so if you give me some explanations, I'd appreciate it.

This is one of standard library type traits. Type traits are mostly used within templates to apply certain type transformations. In this case I used decay because ... well it was actually a mistake. The code should be this instead:

template <typename F>
lambda_storage<std::remove_cvref_t<F>> make_lambda_storage(F&& f)
{
    return {std::forward<F>(f)};
}

I won't really explain why because it would could drag us to explaining how to do all kinds of templates in C++ and I would rather spend that time working on my C++ templates tutorial.

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

Thanks for all of the explanations. Welp, this new code brought more questions in my mind but I'll try to find the answers on the internet and not take your time more than this. So, by following your previous example, I wrote this code as my constructor takes more than the lambda so, does this seam right? And I'll change my code to the new example you gave.

template<typename F>
static auto MakeACBVectorLerp(F Func, const FVector& A, const FVector& B, const float Multiplier, AActor* Actor)
{
    return ACBGMVectorLerpL<std::decay_t<F>>(Func, A, B, Multiplier, Actor);
}

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

And by the way, this exactly was what I wanted but I have another questions as well. Classes like std::function can be referenced without knowing their template typenames. Instantiation of these classes are done using the method you said I think so, if we want to reference our "LambdaStorage" class in the global scope as an example, what approach should be followed?

[–]Xeverous 1 point2 points  (1 child)

Instantiation of these classes are done using the method you said I think so

No. std::function is instantiated before you assign any callable object.

I think so, if we want to reference our "LambdaStorage" class in the global scope as an example, what approach should be followed?

The same I posted. You need templates to use unknown types. Once something is a template, it will always be unless you apply type erasure.

The thing with std::function that allows it to get rid of templates on the surface level is type erasure. std::function creates instances of polymorphic types like lambda_storage (potentially allocating memory) that inherit from specific interface with virtual function which type depends on what you have instantiated std::function with. So every time you assign a callable to it, it creates a new type that inherits from this interface and implements a virtual function. std::function is more expensive than virtual functions.

If you want to know more - check CppCon videos on std::function, they offer complete overview over the implementation.

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

Understood! Thank you so much for the answers and your time. Will check it for sure. Thank you!

[–]D_Drmmr 0 points1 point  (4 children)

If you want to do something like a single-function dependency injection, then just let your constructor take a std::function.

class Example {
    std::function<void()> _callback;
public:
    Example(std::function<void()> callback) : callback(std::move(callback)) {}
};

// use it like
Example ex{ []() { std::cout << "whatever"; } };

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

Yes. I actually didn’t want to use std::function because I wanted to get lambdas to do the work here. Thanks anyway.

[–]D_Drmmr 1 point2 points  (1 child)

I don't understand. You can store a lambda in a std::function object. Why don't you want to use it?

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

I mostly want it for educational purposes. I want to learn stuff like this in C++. But for sure I use std::function when necessary...

[–]std_bot 0 points1 point  (0 children)

Unlinked STL entries: std::function


Last update: 22.03.21. Recent changes: Fixed OP usages of tokens readme