leftaroundabout
leftaroundabout

Reputation: 120731

Why can non-capturing lambdas not be default-constructed, and is it possible to work around this?

It is possible to use the type of a lambda as a template argument, like

template<typename InArg, typename Function>
class selfCompose {
  Function f;
 public:
  selfCompose(Function f): f(f) {}
  auto operator() (InArg x) -> decltype(f(f(x))) {
    return f(f(x));                              }
};

int main() {
  auto f = [](int x){return x*x;};
  std::cout << selfCompose<int, decltype(f)>(f)(4)  //  yields (4²)² = 256
            << std::endl;
  return 0;
}

However, this double use of f is kind of redundant. We can omit passing the lambda's type as the template (casting it to a suitable std::function (at loss of polymorphism – but C++ lambdas aren't parametrically polymorphic anyway)), however I have an application where I'd much prefer not having to pass its value to the constructor (because I'd like to use my class's initialisations themselves as a template parameter, where a particular constructor signature is expected). I'd like it to work like

template<class InArg, class Function>
class selfCompose {
  Function f;
 public:
  selfCompose() {}  // default constructor for f
  auto operator() (InArg x) -> decltype(f(f(x))) {
    return f(f(x));                              }
};

int main() {
  auto f = [](int x){return x*x;};
  std::cout << selfCompose<int, decltype(f)>()(4) << std::endl;
  return 0;
}

but this doesn't compile because lambdas have a deleted default constructor. Which is of course inevitable for capturing lambdas, but for simple ones like the one in my example this doesn't make much sense to me: they don't need to reference any local variables.

Is there some other way to get this functionality, or do I have to resort to defining the lambda old-fashionly as a named class?

struct myFun {
  auto operator() (int x) -> int {return x*x;}
};

(of course, the lambda functions I'd like to use aren't quite as simple as x → x², so just selecting from a few standard function classes wouldn't be flexible enough)

Upvotes: 5

Views: 426

Answers (3)

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361612

Benjamin already posted a nice workaround for your case. You could use that.

My solution is just an improved version of Benjamin's answer where you don't have to specify the lambda parameter type. So instead of writing this:

make_selfCompose<int>(f)(4); //Benjamin's solution

you can write just this:

make_selfCompose(f)(4); //improved - absence `int` template argument.

and let make_selfCompose deduce the lambda parameter type itself.

For this solution, lets write first function_traits class template as:

#include <tuple>

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename C, typename R, typename... A>
struct function_traits<R(C::*)(A...) const>
{
   template <size_t i>
   struct arg
   {
      typedef typename std::tuple_element<i, std::tuple<A...>>::type type;
   };
};

Then here is an improved version of make_selfCompose:

template<typename Fun> //<--- now it has one template parameter
selfCompose<typename function_traits<Fun>::template arg<0>::type, Fun>
make_selfCompose(Fun f)
{
  typedef typename function_traits<Fun>::template arg<0>::type InArg; //deduce it
  return selfCompose<InArg, decltype(f)>(f);
}

Here is test program:

int main() {
  auto f = [](int x){return x*x;};
  std::cout << make_selfCompose(f)(4)  //notice the relief here!
            << std::endl;
  return 0;
}

See an online demo

Hope that helps. :-)

Upvotes: -2

Luc Danton
Luc Danton

Reputation: 35449

If selfCompose is made polymorphic, there is no need to either explicitly pass the parameter type or to inspect the stored functor type. The latter means that it will also be able to deal with polymorphic functors.

template<typename Functor>
struct self_compose_type {
    // Omitting encapsulation for brevity
    Functor functor;

    // It is possible to overload operator() to deal
    // with all cv and ref qualifiers combinations
    template<typename... T>
    auto operator()(T&&... t)
    // Don't use std::result_of for the return type
    -> decltype( std::declval<Functor&>()(std::declval<T>()...) )
    { return functor(std::forward<T>(t)...); }
};

template<typename Functor>
self_compose_type<Functor>
self_compose(Functor&& functor)
{ return { std::forward<Functor>(functor) }; }

// C++03 style:
template<typename Functor>
self_compose_type<typename std::decay<Functor>::type>
make_self_compose(Functor&& functor)
{ return { std::forward<Functor>(functor) }; }

It is not necessary to make the operator() variadic, you can make it accept exactly one argument if you so wish.

Upvotes: 1

Benjamin Lindley
Benjamin Lindley

Reputation: 103733

You can follow the example of functions like make_pair and make_shared:

template<typename InArg, typename Function>
selfCompose<InArg, Function> make_selfCompose(Function f)
{
  return selfCompose<InArg, decltype(f)>(f);
}

int main() {
  auto f = [](int x){return x*x;};
  std::cout << make_selfCompose<int>(f)(4)
            << std::endl;
  return 0;
}

Upvotes: 5

Related Questions