Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return type of function that returns an std::function [duplicate]

I have a function that should return an std::function of the same type as that function. Basically I want something like this:

using RetType = std::function<RetType(void)>;

which obviously will not compile. How do I correctly declare the return type?

like image 989
Sidharth Mudgal Avatar asked Sep 28 '22 09:09

Sidharth Mudgal


1 Answers

You cannot use std::function that way.

You can roll your own, but it will take some work.

Here is a sketch:

template<class T, class A, class B>
struct sub{using type=T;};
template<class T, class A, class B>
using sub_t=typename sub<T,A,B>::type;
template<class T, class B>
struct sub<T,T,B>{using type=B;};
template<class R,class...Args,class A,class B>
struct sub<R(Args...),A,B>{
  using type=sub_t<R,A,B>(sub_t<Args,A,B>...);
};

write the above. It takes a type T, and if it matches A it returns B. Otherwise it returns T. It also works on function signatures.

We can use this with a 'flag' type in a signature to replace with the type of the function object itself:

struct recurse{}; // flag type

// not needed in C++14:
template<class Sig>
using result_of_t=typename std::result_of<Sig>::type;

template<class Sig>
struct func {
  using Sig2=sub_t<Sig,recurse,func>;
  using function = std::function<Sig2>;
  function impl;
  template<class...Ts>
  result_of_t<function const&(Ts...)>
  operator()(Ts&&...ts)const
  {
    return impl(std::forward<Ts>(ts)...);
  }
};

Then func<recurse()> is a function object that when called, returns a func<recurse()>.

Turns out the implementation is as simple as storing a std::function<Sig2> and calling it. The above code lacks polish -- you'd want constructors, more operators, privacy, etc.

live example

Note that a y-combinator might be useful if you want to avoid having to capture a copy of yourself by reference in order to return *this in a lambda, as capture by reference implies a limited lifetime (and avoid using shared ptr).

Other work that is useful would be enhancing sub to handle references-to-A, or even templates that contain A as an argument. (the general sub algorithm is not doable in C++, as C++ does not have full meta-template capabilities, but handling every template class currently in std is easy: they are all pure type templates, or std::array).


For completeness, you can add this to sub:

// optional stuff for completeness:
template<class T,class A,class B>
struct sub<T&,A,B>{
  using type=sub_t<T,A,B>&;
};
template<class T,class A,class B>
struct sub<T*,A,B>{
  using type=sub_t<T,A,B>*;
};
template<template<class...>class Z,class... Ts,class A,class B>
struct sub<Z<Ts...>,A,B>{
  using type=Z<sub_t<Ts,A,B>...>;
};
template<template<class,size_t>class Z,class T,size_t n,class A,class B>
struct sub<Z<T,n>,A,B>{
  using type=Z<sub_t<T,A,B>,n>;
};
template<class T,size_t n,class A,class B>
struct sub<T[n],A,B>{
  using type=sub_t<T,A,B>[n];
};
template<class T,class A,class B>
struct sub<T[],A,B>{
  using type=sub_t<T,A,B>[];
};
template<class T,class A,class B>
struct sub<T const,A,B>{
  using type=sub_t<T,A,B> const;
};
template<class T,class A,class B>
struct sub<T volatile const,A,B>{
  using type=sub_t<T,A,B> volatile const;
};
template<class T,class A,class B>
struct sub<T volatile,A,B>{
  using type=sub_t<T,A,B> volatile;
};

And it now works recursively on many templates, on arrays, references, and pointers, and with cv-qualified types. This allows you to write something like:

func< std::vector<recurse>() >

which is a function object whose operator() returns a func< std::vector<recurse>() >.

Note that this procedure isn't quite perfect, as if some_template<recurse> isn't a valid template instantiation, the above won't work. A stranger version that takes potentially applied templates and arguments, does the substitution, then the application, would be required in such cases.

like image 169
Yakk - Adam Nevraumont Avatar answered Oct 06 '22 19:10

Yakk - Adam Nevraumont