1

I'd like to create a function that takes a weak pointer and any kind of functor (lambda, std::function, whatever) and returns a new functor that only executes the original functor when the pointer was not removed in the meantime (so let's assume there is a WeakPointer type with such semantics). This should all work for any functor without having to specify explicitly the functor signature through template parameters or a cast.

EDIT: Some commenters have pointed out that std::function - which I used in my approach - might not be needed at all and neither might the lambda (though in my original question I also forgot to mention that I need to capture the weak pointer parameter), so any alternative solution that solves the general problem is of course is also highly appreciated, maybe I didn't think enough outside the box and was to focused on using a lambda + std::function. In any case, here goes what I tried so far:

template<typename... ArgumentTypes>
inline std::function<void(ArgumentTypes...)> wrap(WeakPointer pWeakPointer, const std::function<void(ArgumentTypes...)>&& fun)
{
    return [=] (ArgumentTypes... args)
    {
        if(pWeakPointer)
        {
            fun(args...);
        }
    };
}

This works well without having to explicitly specify the argument types if I pass an std::function, but fails if I pass a lambda expression. I guess this because the std::function constructor ambiguity as asked in this question. In any case, I tried the following helper to be able to capture any kind of function:

template<typename F, typename... ArgumentTypes>
inline function<void(ArgumentTypes...)> wrap(WeakPointer pWeakPointer, const F&& fun)
{
    return wrap(pWeakPointer, std::function<void(ArgumentTypes...)>(fun));
}

This now works for lambdas that don't have parameters but fails for other ones, since it always instantiates ArgumentTypes... with an empty set.

I can think of two solution to the problem, but didn't manage to implement either of them:

  1. Make sure that the correct std::function (or another Functor helper type) is created for a lambda, i.e. that a lambda with signature R(T1) results in a std::function(R(T1)) so that the ArgumentTypes... will be correctly deduced
  2. Do not put the ArgumentTypes... as a template parameter instead have some other way (boost?) to get the argument pack from the lambda/functor, so I could do something like this:

-

template<typename F>
inline auto wrap(WeakPointer pWeakPointer, const F&& fun) -> std::function<void(arg_pack_from_functor(fun))>
{
    return wrap(pWeakPointer, std::function<void(arg_pack_from_functor(fun))(fun));
}
Community
  • 1
  • 1
Janick Bernet
  • 20,544
  • 2
  • 29
  • 55
  • 2
    Just forget about `std::function`. It's a polymorphic *container* for callables of a known signature. Clearly not what you want. Why do you think you need `std::function`? – R. Martinho Fernandes Jun 27 '13 at 10:42
  • Because otherwise I have no way to specify the `Arguments...` in the signature at all, which only leaves option 2. As I said, if I had a way to have a more specific function handle, I would of course use that. But AFAIK to specify function signatures there are either function pointers, which do not work for all lambdas, or `std::function`. – Janick Bernet Jun 27 '13 at 10:53
  • 2
    I'll repeat what `std::function` is: It's a polymorphic container for callables of *a known signature*. Note that *you don't know the signature*. Why do you need to specify the `Arguments...` at all? – R. Martinho Fernandes Jun 27 '13 at 10:54
  • Because I need them in the lambda that the wrap method returns. – Janick Bernet Jun 27 '13 at 10:59
  • So the problem you are trying to solve exists because of the solution you have? – R. Martinho Fernandes Jun 27 '13 at 11:00
  • Yes, agreed, and KennyTm's answer that just came in suggest I might not need the lambda. – Janick Bernet Jun 27 '13 at 11:02
  • 3
    What I am trying to say is that you should forget about your solution and find your problem first. – R. Martinho Fernandes Jun 27 '13 at 11:03
  • I made the question more specific to the actual problem I'm trying to solve and added that the solution could be completely different from what I've tried so far. – Janick Bernet Jun 27 '13 at 11:17
  • just saying: a lambda is not a weak pointer... – Alexander Oh Jun 27 '13 at 12:49
  • No it isn't and I hope I didn't write anything along those lines. I actually didn't want to bring the whole weak pointer thing into this, but without it my question was too general. The whole thing with the lambda was that a) I want to *pass* lambdas to be wrapped and b) my implementation used a lambda because that seemed the cleanest way to go. It's a bit sad that KennyTM's solution, which works, still needs what I would call 'old style' functors. – Janick Bernet Jun 27 '13 at 12:54

2 Answers2

3

You don't have to use a lambda.

#include <iostream>
#include <type_traits>

template <typename F>
struct Wrapper {
    F f;

    template <typename... T>
    auto operator()(T&&... args) -> typename std::result_of<F(T...)>::type {
        std::cout << "calling f with " << sizeof...(args) << " arguments.\n";
        return f(std::forward<T>(args)...);
    }
};

template <typename F>
Wrapper<F> wrap(F&& f) {
    return {std::forward<F>(f)};
}

int main() {
    auto f = wrap([](int x, int y) { return x + y; });
    std::cout << f(2, 3) << std::endl;
    return 0;
}
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • Note how this isn't very different from `int main() { auto f = [](int x, int y) { return x + y; }; std::cout << f(2, 3) << std::endl; return 0; }` (except for the output statement) – R. Martinho Fernandes Jun 27 '13 at 11:01
  • Or `bool f(int x, int y) { return x + y; } int main() { std::cout << f(2, 3) << std::endl; }` – Lightness Races in Orbit Jun 27 '13 at 11:12
  • I rewrote my question to be more specific to the actual problem. The main difference to the solution you presented is that there is an additional parameter that has to be captured. If you could adapt your answer to that would be great. – Janick Bernet Jun 27 '13 at 11:21
1

Assuming the weak pointer takes the place of the first argument, here's how I would do it with a generic lambda (with move captures) and if C++ would allow me to return such a lambda:

template<typename Functor, typename Arg, typename... Args>
auto wrap(Functor&& functor, Arg&& arg)
{
    return [functor = std::forward<Functor>(functor)
           , arg = std::forward<Arg>(arg)]<typename... Rest>(Rest&&... rest)
    {
        if(auto e = arg.lock()) {
            return functor(*e, std::forward<Rest>(rest)...);
        } else {
            // Let's handwave this for the time being
        }
     };
}

It is possible to translate this hypothetical code into actual C++11 code if we manually 'unroll' the generic lambda into a polymorphic functor:

template<typename F, typename Pointer>
struct wrap_type {
    F f;
    Pointer pointer;

    template<typename... Rest>
    auto operator()(Rest&&... rest)
    -> decltype( f(*pointer.lock(), std::forward<Rest>(rest)...) )
    {
        if(auto p = lock()) {
            return f(*p, std::forward<Rest>(rest)...);
        } else {
            // Handle
        }
    }
};

template<typename F, typename Pointer>
wrap_type<typename std::decay<F>::type, typename std::decay<Pointer>::type>
wrap(F&& f, Pointer&& pointer)
{ return { std::forward<F>(f), std::forward<Pointer>(pointer) }; }

There are two straightforward options for handling the case where the pointer has expired: either propagate an exception, or return an out-of-band value. In the latter case the return type would become e.g. optional<decltype( f(*pointer.lock(), std::forward<Rest>(rest)...) )> and // Handle would become return {};.

Example code to see everything in action.

[ Exercise for the ambitious: improve the code so that it's possible to use auto g = wrap(f, w, 4); auto r = g();. Then, if it's not already the case, improve it further so that auto g = wrap(f, w1, 4, w5); is also possible and 'does the right thing'. ]

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • The whole thing that we have to unroll the lambda bothers me. I was really hoping it can be done using a lambda and with concise code (i.e little boilerplate), since that seems to me the main advantage of C++11. Pitty :( – Janick Bernet Jun 27 '13 at 13:40
  • Also, like in KennyTM's solution, what we return is some polymorphic type, since the `operator()` is templated. Ideally, at the point we instantiate the wrap function with specific types we would know what type to return, which is what I tried to model with my approach (hence the use of `std::function` with a templated signature). – Janick Bernet Jun 27 '13 at 13:54
  • @inflagranti No, you can't know at the site. Functors can be polymorphic. You can in fact use a monomorphic lambda, but it's far from concise because you have to spell out the return type rather than have it deduced. And you have to do that spelling at each use site. – Luc Danton Jun 27 '13 at 13:56
  • So if I understand you correctly you're saying that the input Functor could have a templated `operator()` and hence be polymorphic and that's why the return type has to be polymorphic, too? – Janick Bernet Jun 27 '13 at 14:22
  • @inflagranti That's one possibility, yes. Overloading is another. (It's not unheard of to have `operator()` overloaded on constness for instance.) To clarify when I said you can use a monomorphic lambda I meant that instead of using `wrap` altogether. All in all, what prevents you from doing what you want 'naturally' is a conspiracy of several things at once, most of which C++14 intends to fix. – Luc Danton Jun 27 '13 at 14:52