C++14 introduce generic lambdas (when using the auto keyword in the lambda's signatures).
Is there a way to store them in a vector with C++17 ?
I know about this existing question, but it doesn't suit my needs : Can I have a std::vector of template function pointers?
Here is a sample code illustrating what I would like to do. (Please see the notes at the bottom before answering)
#include <functional>
#include <vector>
struct A {
void doSomething() {
printf("A::doSomething()\n");
}
void doSomethingElse() {
printf("A::doSomethingElse()\n");
}
};
struct B {
void doSomething() {
printf("B::doSomething()\n");
}
void doSomethingElse() {
printf("B::doSomethingElse()\n");
}
};
struct TestRunner {
static void run(auto &actions) {
A a;
for (auto &action : actions) action(a);
B b;
for (auto &action : actions) action(b); // I would like to do it
// C c; ...
}
};
void testCase1() {
std::vector<std::function<void(A&)>> actions; // Here should be something generic instead of A
actions.emplace_back([](auto &x) {
x.doSomething();
});
actions.emplace_back([](auto &x) {
x.doSomethingElse();
});
// actions.emplace_back(...) ...
TestRunner::run(actions);
}
void testCase2() {
std::vector<std::function<void(A&)>> actions; // Here should be something generic instead of A
actions.emplace_back([](auto &x) {
x.doSomething();
x.doSomethingElse();
});
actions.emplace_back([](auto &x) {
x.doSomethingElse();
x.doSomething();
});
// actions.emplace_back(...) ...
TestRunner::run(actions);
}
// ... more test cases : possibly thousands of them
// => we cannot ennumerate them all (in order to use a variant type for the actions signatures for example)
int main() {
testCase1();
testCase2();
return 0;
}
NOTES :
A
, B
and TestRunner
cannot be changed, only the code of the test casesIt follow a possible solution (that I wouldn't recommend, but you explicitly said that you don't want to discuss if it's good or wrong and so on).
As requested, A
, B
and TestRunner
have not been changed (put aside the fact that auto
is not a valid function parameter for TestRunner
and I set it accordingly).
If you can slightly change TestRunner
, the whole thing can be improved.
That being said, here is the code:
#include <functional>
#include <vector>
#include <iostream>
#include <utility>
#include <memory>
#include <type_traits>
struct A {
void doSomething() {
std::cout << "A::doSomething()" << std::endl;
}
void doSomethingElse() {
std::cout << "A::doSomethingElse()" << std::endl;
}
};
struct B {
void doSomething() {
std::cout << "B::doSomething()" << std::endl;
}
void doSomethingElse() {
std::cout << "B::doSomethingElse()" << std::endl;
}
};
struct Base {
virtual void operator()(A &) = 0;
virtual void operator()(B &) = 0;
};
template<typename L>
struct Wrapper: Base, L {
Wrapper(L &&l): L{std::forward<L>(l)} {}
void operator()(A &a) { L::operator()(a); }
void operator()(B &b) { L::operator()(b); }
};
struct TestRunner {
static void run(std::vector<std::reference_wrapper<Base>> &actions) {
A a;
for (auto &action : actions) action(a);
B b;
for (auto &action : actions) action(b);
}
};
void testCase1() {
auto l1 = [](auto &x) { x.doSomething(); };
auto l2 = [](auto &x) { x.doSomethingElse(); };
auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
auto w2 = Wrapper<decltype(l2)>{std::move(l2)};
std::vector<std::reference_wrapper<Base>> actions;
actions.push_back(std::ref(static_cast<Base &>(w1)));
actions.push_back(std::ref(static_cast<Base &>(w2)));
TestRunner::run(actions);
}
void testCase2() {
auto l1 = [](auto &x) {
x.doSomething();
x.doSomethingElse();
};
auto l2 = [](auto &x) {
x.doSomethingElse();
x.doSomething();
};
auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
auto w2 = Wrapper<decltype(l2)>{std::move(l2)};
std::vector<std::reference_wrapper<Base>> actions;
actions.push_back(std::ref(static_cast<Base &>(w1)));
actions.push_back(std::ref(static_cast<Base &>(w2)));
TestRunner::run(actions);
}
int main() {
testCase1();
testCase2();
return 0;
}
I can't see a way to store non-homogeneous lambdas in a vector, for they simply have non-homogeneous types.
Anyway, by defining an interface (see Base
) and using a template class (see Wrapper
) that inherits from the given interface and a lambda, we can forward the requests to the given generic lambda and still have an homogeneous interface.
In other terms, the key part of the solution are the following classes:
struct Base {
virtual void operator()(A &) = 0;
virtual void operator()(B &) = 0;
};
template<typename L>
struct Wrapper: Base, L {
Wrapper(L &&l): L{std::forward<L>(l)} {}
void operator()(A &a) { L::operator()(a); }
void operator()(B &b) { L::operator()(b); }
};
Where a wrapper can be created from a lambda as it follows:
auto l1 = [](auto &) { /* ... */ };
auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
Unfortunately, for the requirement was to not modify TestRunner
, I had to use std::ref
and std::reference_wrapper
to be able to put references in the vector.
See it on wandbox.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With