Variadi c конструктор шаблона, смешивающий значения l и значения в кортеже - PullRequest
0 голосов
/ 27 апреля 2020

Я хочу отложить создание (большой) группы классов, среди которых будет реализовано только небольшое подмножество. Для этого я пытаюсь захватить аргументы для конструкторов в классе c variadi, который хранит их в кортеже.

Моя проблема заключается в том, что мне удалось сохранить только ссылки или копии. Однако мне нужно переслать оба вида конструкторам сразу, или я получаю проблемы с областью видимости.

#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();
    }
}

Используя rvalues ​​Args..., конструкторы получают значения, но ссылка на i (неправильно) без изменений:

1 0
1 1
1 2

Используя переадресацию ссылок Args&&..., конструкторы получают ссылку правильно, но за пределами области видимости копии j:

2 1228009304
2 1228009304
2 1228009304

1 Ответ

2 голосов
/ 27 апреля 2020

Единственный способ сохранить временные значения в живых - это сохранить их в кортеже путем копирования. В противном случае, сохраняя ссылки, вы получите их как висячие .

. Вы можете использовать reference_wrapper для хранения i как-бы по ссылке.

Изменения:

  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)...);

используйте std::decay_t<Args>... для хранения всех параметров пакета по значению (операция копирования reference_wrapper стоит дешево).

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)
    { }

, поскольку Args разрушены, просто сохраните: tuple<Args...> и сделать так, чтобы конструктор использовал ссылку для пересылки, чтобы избежать избыточных копий при вызове конструктора.

Как вывод:

2 1
2 2 
2 3

Живая демонстрация

...