0
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ what's the point of the std::forward on the operator T() -- doesn't that make it possible that _value would get moved out and no longer be accessible via further calls to operator T()? or if T can never be an rvalue reference type, then it seems the forward is pointless. or maybe I don't quite understand what forward does -- or maybe not exactly what operator t does \$\endgroup\$ – xaxxon May 3 '16 at 6:32
  • \$\begingroup\$ @xaxxon You are correct that "value_ would get moved out and no longer be accessible via further calls to operator T()", but that's actually the point. Please see my update. \$\endgroup\$ – Zizheng Tai May 3 '16 at 6:36
  • \$\begingroup\$ What behavior of std::bind are you trying to avoid by using your Forward class? \$\endgroup\$ – xaxxon May 3 '16 at 6:40
  • \$\begingroup\$ @xaxxon Updated. This is a convoluted solution, maybe (and I hope) there is a better way! \$\endgroup\$ – Zizheng Tai May 3 '16 at 6:42
  • \$\begingroup\$ @xaxxon godbolt.org/g/VhbOhe This is when it stops working for me...maybe I miss interpreted the cause of the error? \$\endgroup\$ – Zizheng Tai May 3 '16 at 6:57
2
\$\begingroup\$

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";
}
\$\endgroup\$
  • \$\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 May 8 '16 at 21:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.