2

I need a class which will work as a deferred factory, saving the parameters to create another class and invoking make_unique later in time. So far I'm not having any luck getting a variadic template version to work. Any help would be appreciated (minimal non-working version below).

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

EDIT: To clarify, I do not need to use std::bind, and in fact I would like the class to store a copy of the arguments by value to avoid problems with temporary objects. Also updated label to C++14.

Lazward
  • 205
  • 2
  • 8
  • How do you want to handle (as in, forbid) temporaries for this? Note that if any of `args` is a temporary then you have no way of keeping it alive (because unlike _local_ `const T&`, _class member_ `const T&` do not provide lifetime extension, see https://stackoverflow.com/questions/2784262/does-a-const-reference-class-member-prolong-the-life-of-a-temporary). I mean, you can require everything to be copyable (and/or moveable) but doing so won't (and cannot) preserve the full semantics of `make_unique`. – Max Langhof Mar 27 '19 at 16:35
  • 1
    Also note that `Args &&... args` only works as forwarding reference when _the function itself_ is templated on `...Args`. You've already lost perfect forwarding in your constructor's signature. – Max Langhof Mar 27 '19 at 16:41
  • I'm unsure std::bind is the proper way to go. I would like the class to store a copy and not a reference of the parameters for later make_unique invocation. – Lazward Mar 27 '19 at 16:42
  • The point of using `make_unique` is the perfect forwarding that it provides (see my forwarding reference comment for why it is a separate function). If you don't care about that, there's no need to use `make_unique`, just create a `std::unique_ptr` directly. – Max Langhof Mar 27 '19 at 16:44
  • 2
    `make_unique` function template has been added only in C++14 so this can't be achieved in C++11. You will probably need to write your own `make_unique` substitution. – user7860670 Mar 27 '19 at 16:46
  • @MaxLanghof As an aside, you probably want – Artyer Mar 27 '19 at 17:35

3 Answers3

1

If you're ok with every argument being copied, then you can skip std::bind and 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();
}

This compiles just fine in C++14: https://godbolt.org/z/owgoXc

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
0

This is the signature of std::make_unique

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

When you specified Args like you did, std::make_unique may only accept rvalues. However, std::bind passes its bound arguments as lvalues, which is why you get an error.

Instead of std::bind, use lambdas instead

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;
};

Note also ConstructLater shouldn't be templated by Args, it defeats the entire purpose of std::function. Either way, the constructor itself will need to be templated in order to perfectly forward its arguments.

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • Ok this is a better solution than mine. I didn't catch the intended use with `std::function` in there. – Max Langhof Mar 27 '19 at 17:02
  • 1
    This is not preserving the original types passed to the constructor. `make_unique` is always called with lvalue reference. So the whole thing is useless and the constructor can be changed to take copies instead and captured in the lambda using `[=]`. ref: https://gcc.godbolt.org/z/xuW7Am – balki Mar 27 '19 at 17:30
  • @balki There, uselessness fixed. – Passer By Mar 28 '19 at 01:42
-1

You can use a lambda but it is undefined behavior as you are saving a reference to temporary

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

For saving the arguments but also preserve original types they were called with, it is more complicated and involves a lot of casts/variadic template magic. I didn't figure out yet but don't see why it is not possible.

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)...);
  };
}
balki
  • 26,394
  • 30
  • 105
  • 151
  • You can capture the arguments by value, but of course that might change their types, and obviously changes the perfect forwarding behaviour. – Useless Mar 27 '19 at 16:39