Отложенный make_unique с вариационными шаблонами - PullRequest
2 голосов
/ 27 марта 2019

Мне нужен класс, который будет работать как отложенная фабрика, сохраняя параметры для создания другого класса и вызывая make_unique позже.Пока что мне не повезло заставить работать вариабельную версию шаблона.Любая помощь будет принята (минимальная нерабочая версия ниже).

template <typename T, typename ... Args>
class ConstructLater
{
public:
    ConstructLater(Args &&... args)
    {
        factory = std::bind(std::make_unique<T, Args...>, std::forward<Args>(args)...);
    }

    std::unique_ptr<T> Later()
    {
         return factory();
    }

private:
    std::function<std::unique_ptr<T>(void)> factory;
};

class Foo { public: Foo(int) { } };

int f()
{
    // None of these work
    ConstructLater<Foo>(3);
    ConstructLater<Foo, int>(6);
}

РЕДАКТИРОВАТЬ: Для пояснения, мне не нужно использовать std :: bind, и на самом деле я хотел бы, чтобы класс для храненияКопирование аргументов по значению, чтобы избежать проблем с временными объектами.Также обновлен ярлык до C ++ 14.

Ответы [ 3 ]

1 голос
/ 27 марта 2019

Если вы согласны с каждым копируемым аргументом, вы можете пропустить std::bind и std::make_unique:

template <typename T, typename ... Args>
class ConstructLater
{
public:
    ConstructLater(Args... args) : _storedArgs(args...)
    {
    }

    std::unique_ptr<T> later()
    {
        return laterHelper(std::make_index_sequence<sizeof...(Args)>{});
    }

private:
    template<std::size_t... I>
    std::unique_ptr<T> laterHelper(std::index_sequence<I...>)
    {
        return std::unique_ptr<T>(new T(std::get<I>(_storedArgs)...));
    }

    std::tuple<Args...> _storedArgs;
};

class Foo { public: Foo(int) { } };

int f()
{
    ConstructLater<Foo, int> cl(6);

    auto foo = cl.later();
}

Это прекрасно компилируется в C ++ 14: https://godbolt.org/z/owgoXc

0 голосов
/ 27 марта 2019

Это подпись std::make_unique

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&...);

Когда вы указали Args, как и вы, std::make_unique может принимать только значения. Однако std::bind передает свои связанные аргументы как lvalues, поэтому вы получаете ошибку.

Вместо std::bind вместо лямбды

template<typename...>
struct pack {};

template<typename T, typename Tup, typename... TArgs, std::size_t... Is>
std::unique_ptr<T> helper(Tup&& tup, pack<TArgs...>, std::index_sequence<Is...>)
{
    return std::make_unique<T>(static_cast<TArgs>(std::get<Is>(tup))...);
}

template <typename T>
class ConstructLater
{
public:
    template<typename... Args>
    ConstructLater(Args&&... args)
        : factory{[tup = std::make_tuple(std::forward<Args>(args)...)]() mutable {
            return helper<T>(std::move(tup), pack<Args&&...>{}, std::index_sequence_for<Args...>{});
        }}
    {
    }

    std::unique_ptr<T> Later()
    {
         return factory();
    }

private:
    std::function<std::unique_ptr<T>(void)> factory;
};

Заметьте также, что ConstructLater не должен быть шаблоном Args, он уничтожает всю цель std::function. В любом случае, сам конструктор должен быть настроен на шаблон для полной пересылки его аргументов.

0 голосов
/ 27 марта 2019

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

    factory = [&]() {
        return std::make_unique<T>(std::forward<Args>(args)...);
    };

Для сохранения аргументов, а также для сохранения исходных типов, с которыми они были вызваны, это более сложно и включает в себя много магии приведения / изменения шаблона. Я еще не понял, но не понимаю, почему это невозможно.

template<class... Args>
ConstructLater(Args &&... args)
{
  std::tuple<TODO...> tup = remove_const_remove_ref<Args...>(args);
  factory = [=]() {
      return std::make_unique<T>(cast_tuple<Args...>(tup)...);
  };
}
...