I'm implementing a lazy constructor in C++. The goal is that for a type T
, lazy<T>(args...)
returns a callable object which, when called, returns T(args...)
.
This is what I have done so far:
#include <functional>
#include <utility>
/**
* Returns an object of type `T` constructed from `args`.
*/
template<typename T, typename... Args>
T make(Args&&... args)
{
return T(std::forward<Args>(args)...);
}
/**
* Wrap a value of type `T` and perfect-forward it when accessed.
*/
template<typename T>
class Forward {
public:
Forward(T &&value) noexcept : value_(std::forward<T>(value))
{}
operator T()
{
return std::forward<T>(value_);
}
private:
T value_;
};
/**
* Returns a callable object which, when called, returns
* an object of type `T` constructed from `args`.
*/
template<typename T, typename... Args>
auto lazy(Args&&... args) -> decltype(
std::bind(
make<T, Args...>,
Forward<Args>(std::forward<Args>(args))...))
{
return std::bind(
make<T, Args...>,
Forward<Args>(std::forward<Args>(args))...);
}
(Contrived) Examples:
// rvalue arguments
auto makePtr = lazy<std::unique_ptr<int>>(new int(123));
auto ptr = makePtr();
// lvalue arguments
int *rawPtr = new int(456);
auto makePtr2 = lazy<std::unique_ptr<int>>(rawPtr);
auto ptr2 = makePtr2();
Can this code be improved (particularly in terms of efficiency)?
UPDATE: The Forward
class is used only as arguments to std::bind
. Rationale:
Suppose we have a type Foo
with constructor Foo(const std::unique_ptr<int> &)
. We can construct it by calling make<Foo, const std::unique_ptr<int> &>(someUniquePtr)
, for example.
However, std::bind(make<Foo, const std::unique_ptr<int> &>, someUniquePtr)()
won't work (I think), because the reference-ness of the second argument to std::bind
will be dropped. Wrapping it inside a std::cref
works, but for packed arguments it seems the Forward
class is the only solution I can come up with that works for all types.
It's possible that I misinterpreted the cause of this but here is an example of what I mean: https://godbolt.org/g/8w2Msa.
1 Answer 1
I think you can really simplify your code with the use of lambdas.
template<typename T, typename... Args>
auto make_lazy(Args&&... args)
{
return [=]{return T(std::move(args)...);};
}
Usage example:
#include <iostream>
class T
{
int v;
public:
T(int v): v(v) {std::cout << "Building T\n";}
friend std::ostream& operator<<(std::ostream& s, T const& out) {return s << "T: " << out.v << "\n";}
};
class S
{
int v;
public:
S(int v): v(v) {std::cout << "Building S\n";}
friend std::ostream& operator<<(std::ostream& s, S const& out) {return s << "S: " << out.v << "\n";}
};
template<typename T, typename... Args>
auto make_lazy(Args&&... args)
{
return [=]{return T(std::move(args)...);};
}
int main()
{
auto lazyT = make_lazy<T>(1);
auto lazyS = make_lazy<S>(2);
std::cout << "Building\n";
std::cout << lazyT() << "\n";
std::cout << lazyS() << "\n";
}
-
\$\begingroup\$ Does the make_lazy actually do what we expect it to do? The capture takes everything by value. (Maybe c++14 can prevent copies) + The members of the lambda are constants, so you need to make your lambda mutable in order to prevent a second copy. \$\endgroup\$JVApen– JVApen2016年05月08日 21:23:51 +00:00Commented May 8, 2016 at 21:23
value_
would get moved out and no longer be accessible via further calls tooperator T()
", but that's actually the point. Please see my update. \$\endgroup\$