1

Take a "lazy" constructor that might have the following interface:

template<class T>
struct LazyConstruct {
   // accept any number of arguments, 
   // which would later be used to construct T
   template<class... U>
   LazyConstruct(U&&... u) {
       // store the arguments somehow
   }
   T& get() {
      if(!data) data.reset( new T( /* unpack the arguments */ ) );
      return *data;
   }
private:
   std::unique_ptr<T> data;
};

What would be a nice way to implement this?

Nick
  • 931
  • 6
  • 17
  • Are you allowed to assume that the lvalue references passed in will be valid when `get` is called? – Pradhan Oct 22 '14 at 03:48
  • yes. or more specifically, the behavior is undefined if not. (just like ref binding in any other context) – Nick Oct 22 '14 at 12:58

4 Answers4

2

Here's a little bit of a convoluted way of doing what you want. The basic idea is to have LazyConstruct store the arguments pack in a tuple, and then unpack the tuple on demand to construct T.

template<class T, class... Args>
struct LazyConstruct {
   // accept any number of arguments, 
   // which would later be used to construct T
   template<class... U>
   LazyConstruct(U&&... u)
   : args(std::make_tuple(std::forward<U>(u)...))
   {
   }

   T& get() {
      if(!data) data = create(std::index_sequence_for<Args...>());
      return *data;
   }

   template<std::size_t... I>
   std::unique_ptr<T> create(std::index_sequence<I...>)
   {
      return std::unique_ptr<T>{new T(std::get<I>(args)...)};
   }

private:
   std::tuple<typename std::decay<Args>::type...> args;
   std::unique_ptr<T> data;
};

I'm making use of C++14's std::index_sequence, if your standard library implementation does not ship this, then there are several examples on SO (this or this) showing how it can be implemented.

Finally a helper function template to construct LazyConstruct instances

template<class T, class... Args>
LazyConstruct<T, Args...> make_LazyConstruct(Args&&... args)
{
    return LazyConstruct<T, Args...>{std::forward<Args>(args)...};
}

Live demo


Another version based on Alf's answer that uses std::function so that LazyConstruct's type doesn't change based on T's constructor signature.

template<class T>
struct LazyConstruct {
   template<class... Args>
   LazyConstruct(Args&&... args)
   : holder([this, args = std::make_tuple(std::forward<Args>(args)...)]() {
            return create(std::index_sequence_for<Args...>(), std::move(args));
       })
   {
   }

   T& get() {
      if(!data) data = holder();
      return *data;
   }

   template<std::size_t... I, class Tuple>
   std::unique_ptr<T> create(std::index_sequence<I...>, Tuple args)
   {
      return std::unique_ptr<T>{new T(std::get<I>(args)...)};
   }

private:
   std::function<std::unique_ptr<T>()> holder;
   std::unique_ptr<T> data;
};

Live demo

Community
  • 1
  • 1
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • With the OP's specified interface `LazyConstruct` instances for the same `T` but different constructors, would be of the same type. How can that be achieved with this answer's basic implementation? – Cheers and hth. - Alf Oct 22 '14 at 07:51
  • the original interface was clearly not sufficient, thanks for pointing that out. @Praetorian, cool! – Nick Oct 22 '14 at 12:56
  • @Nick Glad it helped. Alf does have a good point though, one that I missed when posting the answer. It is possible to keep `LazyConstruct`s type the same given different constructors of `T` if you resort to type erasure of some sort as shown in his answer. I've posted another example similar to his; mine uses C++14 init-capture to perfect forward the arguments to the lambda. – Praetorian Oct 22 '14 at 15:19
1

I am not sure about your question, but for lazy initialization I suggest you to use something along the lines of boost::optional<T>. You can delay initialization with it and you will not make use of a pointer and heap memory.

class MyClass {
public:
    void f();
};

void anotherFunc(MyClass & c);

boost::optional<MyClass> mc; //Not initialized, empty, stack memory.

mc = MyClass{};
if (mc != boost::none)
    mc->f();
anotherFunc(*mc);

Documentation is here: Boost.Optional

Germán Diago
  • 7,473
  • 1
  • 36
  • 59
1

The easiest is probably to just capture the arguments in a lambda.

template<class T>
struct LazyConstruct {
   // accept any number of arguments, 
   // which would later be used to construct T
   template<class... U>
   LazyConstruct(U&&... u)
       : create( [=]() -> T* { return new T(u...); } ) 
   {}
   T& get() {
      if(!data) data.reset( data.reset( create() ) );
      return *data;
   }
private:
   std::unique_ptr<T> data;
   std::function<auto()->T*> create;
};

Disclaimer: Code not touched by compiler's hands.

Note: While I'm unable right now to say exactly what's wrong with the idea (it's pretty late), lazy creation doesn't smell right, somehow. I suspect premature optimization.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • I like the type erasure on this one. But does lambda capture work like that? I mean isn't [=] going to copy everything rather than forward? – Nick Oct 22 '14 at 13:17
  • @Nick: It's going to copy the arguments, yes. It's only safe to logically forward if you can guarantee that the lifetime of all actual arguments extend beyond the first call of `get`. – Cheers and hth. - Alf Oct 22 '14 at 13:21
  • That "safety" requirement is the same for any lambda or other ref-binding context; it is fine here as well (which is a detail a failed to emphasize originally). I.e. there doesn't need to be a "guarantee" per se, just a contract. – Nick Oct 22 '14 at 14:09
  • @Nick: [Praetorian's answer](http://stackoverflow.com/a/26501509/464581) turns out to copy as well. See [demo at Coliru](http://coliru.stacked-crooked.com/a/737195788a5e4eeb). At least I fail to prevent it from copying, except I'm sure one can do it by using a reference wrapper as formal argument type, but then you can do that also for the solution in this answer and avoid a multitude of distinct wrapper types for same wrappee. – Cheers and hth. - Alf Oct 22 '14 at 15:00
  • Well, you have a non-moveable type due to the presence of the user defined copy-ctor. If you add a move-ctor you'll see it [being invoked](http://coliru.stacked-crooked.com/a/bdcb64fadede8baf). +1 for your answer. I borrowed it to add another example. – Praetorian Oct 22 '14 at 15:24
  • @Pretorian: It still copies lvalue arguments. First of all that makes it impossible to pass a reference transparently (so that e.g. the constructor might store a pointer to the passed object): with both my and your solution this must be done by explicitly using a reference wrapper as actual argument. Secondly, it might be costly to copy the lvalue argument, e.g. a big `std::vector`. I think one possible solution is to transform the constructor argument types, automating the reference wrapper things. But the forwarding can't be perfectly automated, e.g. considering nullpointers. – Cheers and hth. - Alf Oct 22 '14 at 18:41
0

As per the comment before. You want to delay and capture the arguments.

EDIT: Generalized solution, should work in C++11. Warnings: not tested. apply function is left as an exercise. See here for a possible implementation:

template <class T>
struct make {
    template <class...Args>
    T operator()(Args &&... args) const {
        return T(std::forward<Args>(args)...);
  }
};


template <class T, class... Args>
struct object_builder {

    object_builder(Args... && args) :
        captured_args_(std::forward<Args>(args)...) {}

    T operator()() const {
        return apply(make<T>{},
                 captured_args_);
}

 private:
    std::tuple<Args...> captured_args_;
};


template <class T, class...Args>
object_builder<T, Args...> make_object_builder(Args &&...args) {
    return object_builder<T, Args...>(std::forward<Args>(args)...);
}

int main() {
    //Create builders with captured arguments
    auto scary_monster_builder = 
        make_object_builder<Monster>(scary, "big orc");
    auto easy_monster_builder = make_object_builder<Monster>(easy,
                                                         "small orc");

    //Instantiate objects with the captured arguments from before
    auto a_scary_monster = scary_monster_builder();
    auto an_easy_monster = easy_monster_builder();
}
Germán Diago
  • 7,473
  • 1
  • 36
  • 59