Modern C++ and C# both let you treat functions as first‑class citizens. you can store them, pass them around, and call them later (callback mostly we will use it for professionally). But the way each language does this is very different.
If you’ve ever wondered how C# delegates, Func<>, Action<>, lambda expressions, function pointers, functors, and C++ lambdas all relate to each other, this post gives you the complete picture.
1. Delegates in C#: The Type‑Safe Function Pointer
In C#, a delegate is essentially a type‑safe function pointer. It can point to:
- static methods
- instance methods
- lambdas
- anonymous functions
Example:
public delegate int MyDelegate(int x);
int AddOne(int x) => x + 1;
MyDelegate d = AddOne;
Console.WriteLine(d(5)); // 6
A delegate stores both:
- a pointer to the method
- a reference to the object (if it’s an instance method)
This makes delegates more powerful than raw C++ function pointers.
2. Func<> and Action<>: Built‑In Delegate Types
C# gives you built‑in delegate types so you don’t need to declare your own.
Func<arg, return>
Func<int, int> f = x => x * 2;
Console.WriteLine(f(10)); // 20
Action<arg>
Action<int> print = x => Console.WriteLine(x);
print(5);
These are the most common delegate types in modern C#.
3. C++ Function Pointers: The Old‑School Way
C++ also has function pointers, but they are limited:
int AddOne(int x) { return x + 1; }
int (*fp)(int) = &AddOne;
std::cout << fp(5); // 6
Function pointers cannot:
- store object instances
- capture variables
- behave like closures
They only point to free functions or static methods, not instance method.
4. C++ Functors: The Delegate Equivalent
Before lambdas existed, C++ used functors which is a structs with operator().
struct MyDelegate {
int operator()(int x) const {
return x + 1;
}
};
MyDelegate d;
std::cout << d(5); // 6
This is the closest equivalent to a C# delegate type.
5. C++ Lambdas: The Modern Way
C++ lambdas are syntactic sugar for functors.
auto f = [](int x) {
return x * 2;
};
std::cout << f(10); // 20
Internally, the compiler generates something like:
struct __Lambda {
int operator()(int x) const { return x * 2; }
};
__Lambda f;
So a C++ lambda is literally a functor object with an inline operator().
6. Closures: Capturing Variables (C# vs C++)
C# automatically captures variables:
int y = 10;
Func<int, int> f = x => x + y;
C++ requires explicit capture:
int y = 10;
auto f = [y](int x) {
return x + y;
};
C++ gives you full control:
[y]capture by copy[&y]capture by reference[=]capture everything by copy[&]capture everything by reference[this]capture the object pointer
This explicitness is crucial in async networking.
7. Capturing this: Calling Member Functions
C#
Action a = () => this.DoSomething();
C++
auto a = [this]() {
DoSomething();
};
C++ requires [this] because lambdas do not automatically have access to member functions.
8. Async Callbacks: C# vs C++ Side‑by‑Side
C# async callback
csharp
stream.ReadAsync(buffer).ContinueWith(task => {
Process(buffer);
});
C++ async callback (Boost.Asio)
cpp
auto buf = std::make_shared<std::string>();
asio::async_read(stream, asio::buffer(buf->data(), len),
[this, buf](auto ec, auto bytes) {
Process(*buf);
}
);
Both:
- run later
- capture variables
- keep buffers alive
- call methods
This is where lambdas shine in both languages.
9. Avoiding Lambdas in C++ (If You Want)
If lambdas feel complicated, you can use std::bind:
cpp
asio::async_read(stream, asio::buffer(buf->data(), len),
std::bind(&MyClass::OnReadComplete, this, buf,
std::placeholders::_1,
std::placeholders::_2)
);
Or a functor:
struct Handler {
MyClass* self;
std::shared_ptr<std::string> buf;
void operator()(auto ec, auto bytes) {
self->Process(*buf);
}
};
But lambdas are cleaner once you understand them.
10. The Complete Mapping Table
| Concept | C# | C++ |
|---|---|---|
| Inline function | x => x * 2 | [](int x){ return x * 2; } |
| Function with return | Func<int,int> | auto f = [](int){} |
| Function with no return | Action<int> | auto f = [](int){} |
| Delegate type | delegate int D(int) | struct { int operator()(int); } |
| Closure | captures variables | [x](){} |
Capturing this | () => this.Method() | [this](){ Method(); } |
| Async callback | ContinueWith(...) | async_read(..., [](){}) |
Note: c++ limbda return type is option e.g
[](int x) -> int { return x * 2; }
is the same as
[](int x){ return x * 2; }
since return type is optional.
Final Thoughts
C# and C++ both give you powerful tools for treating functions as objects, but they approach the problem differently:
- C# delegates are object‑aware function pointers.
- Func<> and Action<> are built‑in delegate types.
- C++ lambdas are functors with inline
operator(). - C++ captures give you precise control over lifetime and memory.
- Async callbacks in both languages rely heavily on lambdas/closures.
Once you understand the mapping, switching between C# and C++ becomes natural, especially when writing asynchronous, event‑driven, or callback‑heavy code.


















