Functor
A functor is an object that acts like a function, using the overloaded operator()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct greaters{
greaters(){}
operator()(int a, int b){return a > b;}
}
struct increment{
private:
int a
public:
increment(int a){a = a;}
operator()(int b){return b+a}
}
std::transform(arr, arr+n, arr, increment(a)); //equivalent to calling increment(a) first.
std::make_heap(arr, arr+n, greaters()); //calls constructor first, then inside the function, it will call the overloaded().
Lambda
Lambda Basics
Lambda functions are introduced when?
Generic lambda (cpp14+) can take in an arbitrary type, with auto
One example of lambda is when working with lots of memcpy
1
2
3
4
5
6
7
// You can use a generic lambda (available since C++14) to make copyToBuffer work with any type:
size_t pos = 0;
auto copyToBuffer = [&](const auto& variable) {
memcpy(buffer + pos, &variable, sizeof(variable));
pos += sizeof(variable);
}
Generic Lambda also works with different input types, due to type deduction:
1
2
3
4
5
auto add = [](auto a, auto b) {
std::cout << b << std::endl; // Print second argument
return a; // Return first argument
};
add(1, std::string("123")); // Works fine!
Lambda Capture with Initializer (C++ 14)
[idx = 0]
is the initializer value in for each.
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> vec(10, 0);
std::for_each(vec.begin(), vec.end(), [idx=0](int& i)mutable{i = ++idx;});
printf("%d", vec.at(9));
return 0;
}
- what type is idx?
- Here type is deduced, and
idx
isint
. we can do:[idx = 3.14]
(float),[idx = std::string("hello")]
(a string)
- Here type is deduced, and
- By default, lambda capture is const. We need mutable to make sure we can modify the vector
- We can pass a reference int& i into the lambda
std::for_each
is in<algorithm>
Another example:
1
2
3
4
5
6
auto my_lambda = [callback = std::move(callback), serialization = rclcpp::Serialization<MessageT>()](const rosbag2_storage::SerializedBagMessageSharedPtr& msg) mutable {
MessageT ros_msg;
rclcpp::SerializedMessage serialized_msg(*msg->serialized_data);
serialization.deserialize_message(&serialized_msg, &ros_msg);
callback(ros_msg);
};
- Why move callback?
- avoids copying a heavy callback function.
- why
mutable
?- By default, lambdas guarantees captured variables to be constant. here, the internal state of serialization might change. Without mutable, you will see:
error: assignment of member in read-only object
- By default, lambdas guarantees captured variables to be constant. here, the internal state of serialization might change. Without mutable, you will see:
- You can assign new variables in the capture list? (like callback)
- Yes, in C++ 14, this is called “init capture”
Full Template Lambda (C++20)
In a full template lambda, we can specify the input types more explicitly.
1
2
3
4
5
6
7
int main() {
auto multiply = []<typename T>(T a, T b) {
return a * b;
};
std::cout << multiply.operator()<double>(3, 2.5) << "\n"; // 3 is int. If we specify double, it will be implicitly casted. 7.5
std::cout << multiply(3.0, 2.5) << "\n"; // 7.5
}
- Like a regular lambda, we can invoke an instance of the template lambda with the synthesized
operator()
.
One limitation of the template lambda is that it can only be stored as a template parameter template <typename Func>
, not as std::function<int(double, double)>
:
1
2
3
4
5
6
7
8
// Works:
std::function<int(double, double)> multiply = [](auto a, auto b) {
return a * b;
};
// Doesn't work:
std::function<int(double, double)> multiply = []<typename T>(T a, T b) {
return a * b;
};
One slightly longer example is as follows: we want to be able to wrap a function in a generic function for evaluation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <vector>
#include <utility>
// Example function that times execution
template <typename Func>
void evaluate_and_call(Func&& func, const std::string& name, int times) {
std::cout << "Running " << name << " " << times << " times...\n";
for (int i = 0; i < times; ++i) {
func(); // Execute the function
}
}
struct T3{};
int main() {
std::vector<std::pair<size_t, size_t>> matches;
// Generic lambda + template lambda
auto lambda = [&]<typename T3>(
T3& grid, auto& matches) {
// grid.GetClosestPointForCloud(first, second, matches);
};
T3 grid0;
evaluate_and_call(
[&]() { lambda(grid0, matches); }, // so we skipped binding
"Grid0 单线程",
10
);
return 0;
}
[&]() { lambda(grid0, matches); }
is a smart way to bind different types of grid with the lambda function. This is aninline, parameter less
function