Axiverse
Axiverse

Reputation: 1674

Template argument deduction for lambdas

I'm trying to make a helper function that executes a lambda/std::function when called if the given weak_ptr is valid. Currently the following code works, but unfortunately, it requires me to define the template parameters. I'm looking for a way to do this with automatic template argument deduction.

template <typename DependentType, typename... ArgumentTypes>
auto make_dependent(std::weak_ptr<DependentType>& dependent, std::function < void(ArgumentTypes...)> functor) -> decltype(functor)
{
    return [&dependent, functor] (ArgumentTypes... args)
    {
        if (!dependent.expired()) {
            functor(args...);
        }
    };
};

Ideally, I would like to replace the std::function <void(ArgumentTypes...)> with a generic template parameter FunctorType, but then I'm not sure how I would extract arguments from FunctorType. The above code works, the below code is theoretical:

template <typename DependentType, typename FunctorType>
auto make_dependent_ideal(std::weak_ptr<DependentType>& dependent, FunctorType functor) -> decltype(std::function<return_value(functor)(argument_list(functor))>)
{
    return[&dependent, functor](argument_list(functor) args)
    {
        if (!dependent.expired()) {
            functor(args...);
        }
    }
}

Is there any way to do something like this?

Upvotes: 8

Views: 1446

Answers (3)

Richard Smith
Richard Smith

Reputation: 14158

Here's how I'd solve your problem in C++14:

template<typename DependentType, typename FunctorType>
auto make_dependent(std::weak_ptr<DependentType> &dependent, FunctorType functor) {
  return [&dependent, functor](auto &&...args) {
    if (!dependent.expired())
      functor(std::forward<decltype(args)>(args)...);
  }
}

I'm using two C++14 features here:

  • Deduced return type for make_dependent
  • A variadic generic lambda with perfect forwarding to call the inner functor

EDIT: The code above captures dependent by reference, as your original code did. Is that really what you want?

Upvotes: 2

Felix Glas
Felix Glas

Reputation: 15524

You can use a proxy trait class to extract the return type and arguments separately from a single template parameter. The Trait class uses the static function dependent_func to create the lambda you want to return.

template <typename DependentType, typename FunctorType>
struct Trait {};

template <typename DependentType, typename ReturnType, typename... ArgumentTypes>
struct Trait<DependentType, std::function<ReturnType(ArgumentTypes...)>> {
    static std::function<ReturnType(ArgumentTypes...)> dependent_func(const std::weak_ptr<DependentType>& dependent, std::function<ReturnType(ArgumentTypes...)>& functor) {
        return [&dependent, &functor] (ArgumentTypes... args) {
            if (!dependent.expired()) {
                return functor(args...);
            }
        };
    }
};

template <typename DependentType, typename FunctorType>
auto make_dependent_ideal(std::weak_ptr<DependentType>& dependent, FunctorType& functor) -> decltype(Trait<DependentType, FunctorType>::dependent_func(dependent, functor)) {
    return Trait<DependentType, FunctorType>::dependent_func(dependent, functor);
}

For more info about parsing template arguments in this way, look at this question: C++ parsing function-type template argument

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275310

The easiest way to solve your problem with extracting arguments from your parameter is to not extract the arguments from your parameter.

template<typename F, typename C>
struct conditional_forwarder {
  F func;
  C cond;
  template<typename Fin, typename Cin>
  conditional_forwarder( Fin&& f, Cin&& c ):
    func(std::forward<Fin>(f)), cond(std::forward<Cin>(c)) {}
  template<typename... Args>
  void operator()( Args&&... args ) const {
    if (cond())
      func( std::forward<Args>(args)... );
  }
};
template<typename F, typename C>
conditional_forwarder< typename std::decay<F>::type, typename std::decay<C>::type >
make_conditional_forwarder( F&& f, C&& c ) {
  return {std::forward<F>(f), std::forward<C>(c)};
}
// todo: make_dependent_test   

template <typename DependentType, typename FunctorType>
auto make_dependent_ideal(std::weak_ptr<DependentType>& dependent, FunctorType functor)
  -> decltype(make_conditional_forwarder( make_dependent_test(dependent), functor) )
{
  return make_conditional_forwarder( make_dependent_test(dependent), functor);
}

much of this will be easier in C++14.

As an aside, there seems to be a fundamental design flaw: the conditional forwarder should probably aquire a .lock() on the weak_ptr, and then execute functor within that lock, so that the precondition (that the resource is held) holds for the entire call of functor.

I'm also unsure why you are holding a reference to a weak_ptr, when the remote state of a weak_ptr can be copied.

In C++14, you can return something like:

return [=](auto&&... args) mutable {
}

I believe, and the decltype stuff also goes away mostly as functions can deduce their return type more easily.

Upvotes: 5

Related Questions