🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

C++ tutorials

Started by
93 comments, last by taby 1 year, 8 months ago

I also tried your method, but I get an error:

// Q: How do I use templates in conjunction
//    with lambda expressions?

#include <iostream>
using std::cout;
using std::endl;

template <typename T>
class A
{
public:

	T x;

	void print(void)
	{
		cout << x << endl;
	}
};

int main(void)
{
	A<int> a;

	auto lambda = [](const auto& item) { item.print(); };

	lambda(a); // Error

	return 0;
}
Advertisement
#include <iostream>

template<class T>
struct MyTemplate
{
	void Print() const { std::cout << "Data: " << m_data << "\n"; }

	T m_data;
};

//C++03 way
struct Functor
{
    template<class T>
	void operator()(const T& value) { value.Print(); }
};

int main(void)
{
	auto lambda = [] (const auto& value) { value.Print(); }; //This captures your lambda
	
	MyTemplate<size_t> variable;
	variable.m_data = 100;
	
	lambda(variable);
	
	Functor func;
	func(variable); //This should do the same
	
	return 0;
}

You don't need to define another template to hold your lambda, a lambda is not exactly like a function it's a closure, which is a function that can have some state. The functor object is more or less what the compiler generates for you behind the scene, but with much less code you have to write.

https://godbolt.org/z/ozGdxehqW​ you can even see that the way things are called generates the same assembly.

taby said:
template class A { public: T x; void print(void) { cout << x << endl; } };

This error is because that function is not callable through a const obejct, when you make that print function const it works or take a way the const from the reference value in the lambda parameter.

You can even instantly invoke a lambda if you wanted to

auto x = [] () { return 5; }();

Lambdas are much easier to read than functors in places specially when it comes to the std predicates you give std::find_if and the like because the code that is the predicate is in the same line as the find itself instead of a different object. Pre C++11 the functor was your only way to modify this predicate and if you have to deal with a template you can't inline your class definition above the class to find.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

taby said:
So, basically, lambda expressions are little functions (anonymous functors) that can be placed in any scope? I read that they now support templates!

I'm not qualified to teach any of this, but interestingly the way i've walked to finally use lambdas seems fine to describe what they actually are.

The thing i always missed was ability to write little function inside a big function. C++11 allowed to do this indirectly, by allowing to write structs or classes inside a function:

void BigFunction ()
{
	struct Helper
	{
		static int LittleFunction (int a, int b)
		{
			return a+b;
		}
	};
	
	int x = Helper::LittleFunction (1,2);
	int y = Helper::LittleFunction (2,3);
	int z = Helper::LittleFunction (3,4); 
	// yay! finally i don't have to copy paste similar code 3 times! :D 
	// lambdas are introduced with C++11 too, so i should use just that, but i was too lazy to learn the strange syntax for a long time
}	

We could now change the Helper object.
Because it only has one function, we could implement said function in its constructor, to get some more elegant syntax.
Recently i've learned that's exactly what lambdas are, just with even more convenient syntax. So probably that's a good way to look at it.

Regarding lambdas, they can be used to get rid of the cost from using functors, which is similar to a virtual function call. That's worth to share i think. Not everybody might know.
I'm too lazy to write a simple example, so posting some code from my fluid simulator:

#if 0 // slow
	void BlockParticlesAdjacencyIterator (const int block, 
		const float interactRadius, const float delta_x, const float cellMaxRad, 
		std::function<bool (const int pI)> OpenOuterLoop,
		std::function<void (const int aBegin, const int aEnd)> IterateInnerLoop,
		std::function<void (const int pI)> CloseOuterLoop
		) 
#else // fast
	template <typename L0, typename L1, typename L2> 
	void BlockParticlesAdjacencyIterator (const int block, 
		const float interactRadius, const float delta_x, 
		L0 OpenOuterLoop, L1 IterateInnerLoop, L2 CloseOuterLoop) 
#endif

	{
		// this function does complex stuff to iterate particles in a sparse grid cell and its neighbors
		// i need such iteration sheme often, but i want to write the code only once
		// goal is to achieve this without performance penalty (otherwise i would prefer to copy paste the code 10 times)
	}

This is basically a helper function to do complex iteration and spatial indexing stuff.
It needs 3 callbacks to implement the functionalities as expected.
But i don't want classic C callbacks, because they would spoil my code with many little functions. I want them to be in function helper objects, so lambdas. So all functionality is grouped inside one big function using this iterator.
This can be done using std::function, but the extra cost is pretty high in my case.
Using templates instead, i can use my lambdas as desired, and there is no extra cost, at least not with Clang compiler. (With MSVC it's 10% still)
You see i have commented out the slow approach. The fast one will of course increase the size of my binary much more, i guess.

Here some example of using the iterator:

auto CloseOuterLoop = [&](const int pI) // just one of the 3 'callbacks' in from of a lambda as example
{
	//...
};

BlockParticlesAdjacencyIterator (block, interactRadius, delta_x, OpenOuterLoop, IterateInnerLoop, CloseOuterLoop);

I'm still a C programmer at heart, so i can't illustrate or explain this well, but you get my point.
Basically i use those modern features mostly to avoid code duplication and keep things organized. Helps a lot.

I was thinking about using a similar approach to replace the cumbersome method of having a multi threading job system requiring annoying little callbacks for every stuff we want to multi thread.
I saw the dev of the Physics engine i'm using achieved this actually. He writes a little lambda somewhere, and right below he makes it executing from a job system with one line of code. That's really awesome i think. Not sure if he can avoid the cost of functors, but if the task is heavy this cost becomes negligible.

@JoeJ As you can see from the assmbler below (MSVC 19.32 std:C++11) of the code I shared in my last post, that both the lambda and functor are identical. And this should really be the case because when you write a lambda the compiler under the hood will generate an unnamed functor for you (in this case lambda_59cb79aa87f63636561f386f319349cc). So lambd==functor, in case of a functor you need to write a constructor that takes the paramters you capture in “[]” in the lambda expression.

void Functor::operator()(MyTemplate<unsigned int> const &) PROC          ; Functor::operator(), COMDAT
        push    ebp
        mov     ebp, esp
        push    ecx
        mov     DWORD PTR _this$[ebp], ecx
        mov     ecx, DWORD PTR _value$[ebp]
        call    void MyTemplate<unsigned int>::Print(void)const          ; MyTemplate<unsigned int>::Print
        mov     esp, ebp
        pop     ebp
        ret     4
void Functor::operator()(MyTemplate<unsigned int> const &) ENDP          ; Functor::operator()

auto <lambda_59cb79aa87f63636561f386f319349cc>::operator()<MyTemplate<unsigned int> >(MyTemplate<unsigned int> const &)const  PROC ; <lambda_59cb79aa87f63636561f386f319349cc>::operator()<MyTemplate<unsigned int> >, COMDAT
        push    ebp
        mov     ebp, esp
        push    ecx
        mov     DWORD PTR _this$[ebp], ecx
        mov     ecx, DWORD PTR _value$[ebp]
        call    void MyTemplate<unsigned int>::Print(void)const          ; MyTemplate<unsigned int>::Print
        mov     esp, ebp
        pop     ebp
        ret     4
auto <lambda_59cb79aa87f63636561f386f319349cc>::operator()<MyTemplate<unsigned int> >(MyTemplate<unsigned int> const &)const  ENDP ; <lambda_59cb79aa87f63636561f386f319349cc>::operator()<MyTemplate<unsigned int> >

When you look how these are called you will see the same thing.

        lea     ecx, DWORD PTR _variable$[ebp]
        push    ecx
        lea     ecx, DWORD PTR _lambda$[ebp]
        call    auto <lambda_59cb79aa87f63636561f386f319349cc>::operator()<MyTemplate<unsigned int> >(MyTemplate<unsigned int> const &)const  ; <lambda_59cb79aa87f63636561f386f319349cc>::operator()<MyTemplate<unsigned int> >
        lea     edx, DWORD PTR _variable$[ebp]
        push    edx
        lea     ecx, DWORD PTR _func$[ebp]
        call    void Functor::operator()(MyTemplate<unsigned int> const &)   ; Functor::operator()

The reason your solution with the template is faster is because of how std::function works and not because of lambda/functor. In a std::function capture if the object cannot contain the whole of the capture it has to go to the heap to get that memory, in the case of a lambda/functor this memory is on the stack since you are creating that object on the stack and then passing it to the function. The problem with the lambda here is that you can run out of stack space if the lambda/functor captures enough information.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

Thank you guys. It's working now:

https://github.com/sjhalayka/sample_cpp_code/blob/main/s23_lambda_expressions.cpp

NightCreature83 said:
The reason your solution with the template is faster is because of how std::function works and not because of lambda/functor.

Thanks. Actually i have confused functor with std::function.
I must admit i don't know what ‘functor’ means at all, thus my terminology is quite lacking ; )

Functor is a function object which means a class/struct that had the operator()() overloaded.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

NightCreature83 said:
operator()()

Attack of the modern C++ monster shooting with even more confusing and strange syntax :D

the call operator is not modern at all and has been used in the past for filters to std algorithms.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

NightCreature83 said:
the call operator is not modern at all and has been used in the past for filters to std algorithms.

Ah, yes. Really a confusing topic to me. Tried to google, but could not find an example to illustrate convincing purpose and advantage.

But i guess it could be just a one liner, so could you write one?

This topic is closed to new replies.

Advertisement