I'm trying to create an "initializer" function in C++17 that curries call to the constructor of the object so it can deferred to later time, when the rest of the variadic arguments is known.
I've followed the example at c++ lambdas how to capture variadic parameter pack from the upper scope which compiles, but does not handle passing values by references.
The full running example can be found at https://repl.it/repls/IroncladToughExabyte
I copy the code here as well, so that it will be captured for future reference:
#include <iostream>
#include <functional>
#include <memory>
#include <utility>
#include <limits>
using std::cout;
using std::endl;
using std::function;
using std::move;
using std::unique_ptr;
using std::make_unique;
typedef uint8_t MyParam;
struct SomethingBig {
int a;
int b;
};
class GamePlayingAlgorithm {
public:
const MyParam &a_;
const MyParam b_;
GamePlayingAlgorithm(const MyParam &a, MyParam b)
: a_(a), b_(b) {};
virtual void play() = 0;
};
class PassByRef: public GamePlayingAlgorithm {
public:
SomethingBig &stuff_;
inline explicit PassByRef(const MyParam &a, MyParam b, SomethingBig &stuff)
: GamePlayingAlgorithm(a, b), stuff_(stuff) {}
void play() override {
cout << stuff_.a << endl;
}
};
typedef function<unique_ptr<GamePlayingAlgorithm>(const MyParam &, MyParam)>
PreparedAlgorithm;
template<typename...>
struct pack {};
template<typename T, typename Tup, typename... TArgs, std::size_t... Is>
std::unique_ptr<T>
helper(const MyParam &domain, MyParam pl, Tup &&tup, pack<TArgs...>, std::index_sequence<Is...>) {
return std::make_unique<T>(domain, pl, static_cast<TArgs>(std::get<Is>(tup))...);
}
// use tuple packing/unpacking
template<typename T, typename... Args>
PreparedAlgorithm createInitializer1(Args &&... args) {
return [tup = std::make_tuple(std::forward<Args>(args)...)](const MyParam &domain,
MyParam pl) mutable {
return helper<T>(domain,
pl,
std::move(tup),
pack<Args &&...>{},
std::index_sequence_for<Args...>{});
};
}
// use simple forwarding with reference in lambda
template<typename T, typename... Args>
PreparedAlgorithm createInitializer2(Args &&... args) {
return [&](const MyParam &domain, MyParam pl) -> unique_ptr<GamePlayingAlgorithm> {
return make_unique<T>(domain, pl, std::forward<Args>(args) ...);
};
}
int main() {
SomethingBig stuffRef1 = {100, 200};
PreparedAlgorithm preparedRef1 = createInitializer1<PassByRef>(stuffRef1);
auto algRef1 = preparedRef1(1, 1);
cout << "algRef1: ";
algRef1->play();
stuffRef1.a = 500;
cout << "algRef1 (after update): ";
algRef1->play();
SomethingBig stuffRef2 = {100, 200};
PreparedAlgorithm preparedRef2 = createInitializer2<PassByRef>(stuffRef2);
auto algRef2 = preparedRef2(1, 1);
cout << "algRef2: ";
algRef2->play();
stuffRef2.a = 500;
cout << "algRef2 (after update): ";
algRef2->play();
}
The output of running this is:
algRef1: 100
algRef1 (after update): 100
algRef2: 100
algRef2 (after update): 500
The problem is that algRef1
is not updated.
algRef2
is updated, but it is using an undefined operation, and in fact it does break in my larger source code.
The question is -- how the implementation of createInitializer1
or createInitializer2
can be changed so that they are properly defined?
Thanks!