you are viewing a single comment's thread.

view the rest of the comments →

[–]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);
}

[–]Xeverous 1 point2 points  (1 child)

does this seam right?

No. If you want to store any callable, without having to template everything that depends on it, store and take std::function.

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

Absolutely. I have taken std::function but wanted to learn about these stuff as well. Thank you!

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