Let's say I want to store a collection of function pointers (from lambdas), where each functions body is very similar. Writing everything out by hand gets repetitive and hard to manage, so instead I would want to use a capture. However, converting a capturing lambda to a function pointer is not possible and I don't want to use something heavy weight like std::function
.
Here is a currently not working example which demonstrates what I would like to achieve:
#include <vector>
int main() {
std::vector<double(*)(int)> functions;
for (int i = 0; i < 5; ++i) {
functions.push_back([i](int v) -> double { return i * v; });
}
}
How could I acieve something similar that scales for bigger collections without swapping to something like std::function
?
As already established, converting a capturing lambda to a function pointer is not possible. This means that we have to search for another way to get different values into each lambda.
The loop could be unrolled by hand to something like the following:
#include <vector>
int main() {
std::vector<double(*)(int)> functions;
functions.push_back([](int v) -> double { return 0 * v; });
functions.push_back([](int v) -> double { return 1 * v; });
functions.push_back([](int v) -> double { return 2 * v; });
functions.push_back([](int v) -> double { return 3 * v; });
functions.push_back([](int v) -> double { return 4 * v; });
}
But this is of course not really scalable. So lets look for something the language/compiler can do for us. In this case we need to somehow generate the integers from 0 to 5 as compile time constants, since we can't capture any runtime values.
The standard library has std::make_index_sequence
to help us here. With it we can generate our wanted sequence of compile time integers. When we additionally use a fold expression we can "loop" through this sequence and apply it to a lambda.
#include <vector>
#include <iostream>
#include <utility>
int main() {
std::vector<double(*)(int)> functions;
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
(functions.push_back([](int v) -> double { return Is * v; }), ...);
}(std::make_index_sequence<5>{});
for (auto& fp : functions) {
std::cout << fp(2) << ' ';
}
}
Output (live example):
0 2 4 6 8
If there is need for different values, not strictly increasing ones of the same type, it could be done like the following:
[&]<auto... Args>() {
(functions.push_back([](int v) -> double { return Args * v; }), ...);
}.template operator()<0, 1, 1.0/3, -5.0f>();