I want to defer the instantiation of a (large) bunch of classes, among which only a small subset will be instantiated. To do that, I try to capture the arguments to constructors in a variadic class which stores them in a tuple.
My problem is that I only succeeded in storing either references or copies. However, I need to forward both kinds to the constructors, at once, or I get scope problems.
#include <iostream>
#include <utility>
#include <tuple>
#include <vector>
// Interface of the classes which instanciation is being deferred.
struct OpItf {
virtual void operator()() = 0;
virtual ~OpItf() {}
};
struct Operator : public OpItf {
int& _i;
int _j;
// Need both a lvalue reference and a rvalue.
Operator(int& i, int j) : _i(i), _j(j) {}
void operator()() {std::cout << _i << " " << _j << std::endl;}
virtual ~OpItf() {}
};
// The interface of the class managing the actual instanciation.
template<class Itf>
struct ForgeItf {
virtual Itf& instanciate() = 0;
virtual ~ForgeItf() {}
};
template<class Itf, class Op, typename... Args>
struct Forge : public ForgeItf<Itf> {
std::tuple<Args&&...> _args;
Itf* _instanciated;
Forge(Args&&... args) :
_args(std::forward<Args>(args)...),
_instanciated(nullptr)
{ }
Itf& instanciate()
{
if(_instanciated) {
delete _instanciated;
}
_instanciated = op_constructor(_args);
return *_instanciated;
}
virtual ~Forge() { delete _instanciated; }
template<class T>
Op* op_constructor(T& args) { return new Op(std::make_from_tuple<Op>(args)); }
};
// A container of forges.
template<class Itf>
struct Foundry : std::vector< ForgeItf<Itf>* > {
template<class Op, typename... Args>
void add(Args&&... args)
{
auto pfo = new Forge<Itf,Op,Args&&...>(std::forward<Args>(args)...);
this->push_back(pfo);
}
virtual ~Foundry() { for(auto p : *this) { delete p; } }
};
int main() {
Foundry<OpItf> foundry;
int iref = 1;
// Store the constructors parameters.
for(int j=0; j<3; ++j) {
foundry.add< Operator >(iref,j);
}
// Change the referenced parameter before instanciations.
iref = 2;
// Actually instanciate.
for(auto& forge : foundry ) {
auto& op = forge->instanciate();
op();
}
}
Using rvalues Args...
, the constructors get the values, but the reference to i
is (uncorrectly) unchanged:
1 0
1 1
1 2
Using forwarding references Args&&...
, the constructors get the reference correctly, but out-of-scope copies of j
:
2 1228009304
2 1228009304
2 1228009304
The only way to keep temporary values alive is to store them into tuple by copy. Otherwise, by storing references you will have them as dangling.
You can use reference_wrapper
to store i
as-if by reference.
Changes:
foundry.add< Operator >(std::ref(iref),j); // wrap i into reference_wrapper
---
auto pfo = new Forge<Itf,Op,std::decay_t<Args>...>(std::forward<Args>(args)...);
use std::decay_t<Args>...
to store all pack parameters by value (operation of copying reference_wrapper
is cheap).
template<class Itf, class Op, typename... Args>
struct Forge : public ForgeItf<Itf> {
std::tuple<Args...> _args;
Itf* _instanciated;
template<class ... Args2>
Forge(Args2&&... args2) :
_args(std::forward<Args2>(args2)...),
_instanciated(nullptr)
{ }
because Args
are decayed, just store: tuple<Args...>
and make constructor taking forwarding reference to avoid redundant copies when calling constructor.
As output:
2 1
2 2
2 3