How to discover argument and result types from lambda for constructing std::function?

160 views
Skip to first unread message

Frank Birbacher

unread,
Feb 2, 2012, 12:15:53 AM2/2/12
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

I have a class template that contains a boost::function member
variable (shall be std::function in future, but gcc4.6 does not have
it.) The function depends on the template arguments of the class
template. For constructing an instance of the class template I added
a free function template that shall be able to discover the template
arguments---just like std::make_pair does.

The problem lies in an easy definition of a function template that
will accept a lambda type, deduce the argument and result types, and
construct the correct instantiation of the class template. I have
played around with decltype, but I'm not quite happy with my solution.

Is there any easier solution? Are there any predefined type traits or
something to make this shorter? Is relying on &F::operator() fine for
lambda types F?

//code works on gcc 4.6:
#include <boost/function.hpp>
#include <utility>

using boost::function;

/* class to hold some function */
template<typename Arg, typename Result>
struct Holder {
typedef function<Result(Arg)> Func;
Func f;
Holder(Func && newF) : f(std::move(newF)) {}
//...
};

/* free factory method to deduce template arguments */
template<typename Arg, typename Result>
Holder<Arg, Result> mkHolder(function<Result(Arg)> && f)
{
return Holder<Arg, Result>(std::move(f));
}

template<typename F>
struct discover_f_type;
template<typename T, typename Arg, typename Result>
struct discover_f_type<Result (T::*)(Arg) const>
{
typedef boost::function<Result(Arg)> function_type;
};

/* how to deduce template arguments from lambda? */
template<typename F>
auto mkHolder(F && f) -> decltype(mkHolder(
typename discover_f_type<decltype(&F::operator())>
::function_type(f)
))
{
typedef discover_f_type<decltype(&F::operator())> D;
return mkHolder(typename D::function_type(f));
}

int main()
{
// works:
mkHolder([](int const i) { return i+5; });
}

Frank
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
Comment: GPGTools - http://gpgtools.org
Comment: keyserver x-hkp://pool.sks-keyservers.net

iEYEARECAAYFAk8pwU4ACgkQhAOUmAZhnmqJBQCfQB4qSjtCKEwS6hT8QGRqPJjU
lGQAoJIUI8qaFHHD48CEfB+HrLmCPteL
=drM6
-----END PGP SIGNATURE-----


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Daniel Krügler

unread,
Feb 2, 2012, 3:24:08 PM2/2/12
to
On 2012-02-02 06:15, Frank Birbacher wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> I have a class template that contains a boost::function member
> variable (shall be std::function in future, but gcc4.6 does not have
> it.) The function depends on the template arguments of the class
> template. For constructing an instance of the class template I added
> a free function template that shall be able to discover the template
> arguments---just like std::make_pair does.
>
> The problem lies in an easy definition of a function template that
> will accept a lambda type, deduce the argument and result types, and
> construct the correct instantiation of the class template. I have
> played around with decltype, but I'm not quite happy with my solution.
>
> Is there any easier solution?

Some parts of your second overload look unnecessarily complicated to me.
Why shouldn't it be necessary to invoke the first overload on function
objects? Consider

template<typename F>
auto mkHolder(F&& f) ->
typename discover_f_type<decltype(&F::operator())>
::function_type
{
typedef discover_f_type<decltype(&F::operator())> D;
return typename D::function_type(std::forward<F>(f));
}

as an alternative. Further I see at least three things that could be
improved here:

a) Albeit rarely to be expected, there is no reason, why mkHolder could
no be called with an lvalue referring to a lambda closure. In this case
you have to ensure that you remove the reference from the deduced
effective parameter, e.g. like so:

template<typename F>
auto mkHolder(F&& f) ->
typename
discover_f_type<decltype(&std::remove_reference<F>::type::operator())>
::function_type
{
typedef
discover_f_type<decltype(&std::remove_reference<F>::type::operator())> D;
return typename D::function_type(std::forward<F>(f));
}

b) A lambda closure can be declared as mutable, with the effect of
having a non-const operator() overload instead. This means you should
add one further specialization of discover_f_type for the pattern Result
(T::*)(Arg).

c) I like to note that your first mkHolder overload only accepts rvalues
of boost::function, which is asymmetric to your mkHolder(F&&) overload.
I suggest to add one further overload for boost::function accepting
lvalues as well.

> Is relying on&F::operator() fine for lambda types F?

Good question. The current wording seems to specify this quite
precisely, so it looks like a reasonable approach to me.

HTH & Greetings from Bremen,

Daniel Krügler

Frank Birbacher

unread,
Feb 6, 2012, 3:56:33 AM2/6/12
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

Am 02.02.12 21:24, schrieb Daniel Krügler:
> Some parts of your second overload look unnecessarily complicated
> to me. Why shouldn't it be necessary to invoke the first overload
> on function objects?

Hmm, I'm sorry I don't quite understand this question. The combination
of "why", "should not", and "is necessary" does not lead me to an
interpretation that I can make sense of.

> Consider
>
> template<typename F> auto mkHolder(F&& f) -> typename
> discover_f_type<decltype(&F::operator())> ::function_type

Hmm, you mean "mkHolder" shall not construct a "Holder" object anymore,
but only a "boost::function?" This would mean the call will look like

mkHolder(mkHolder([](...){...}))
?

> a) Albeit rarely to be expected, there is no reason, why mkHolder
> could
[snip]
> b) A lambda closure can be declared as mutable, with the effect of
[snip]
> c) I like to note that your first mkHolder overload only accepts
> rvalues
[snip]

Definately. I agree on those points.

>> Is relying on&F::operator() fine for lambda types F?
>
> Good question. The current wording seems to specify this quite
> precisely, so it looks like a reasonable approach to me.

I wonder why there are no nested typedefs in a lambda closure type. I
expected something like in std::unary_function. Well, I can see, that
this is not a generic approach for any number of operator() argument
types, but I expected at least a typedef for something. For example
boost::function defines arg1 to argN. boost::variant defines an mpl
sequence for its types. No agreement on any typedefs for lambdas?

Frank
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
Comment: GPGTools - http://gpgtools.org
Comment: keyserver x-hkp://pool.sks-keyservers.net

iEYEARECAAYFAk8u9qoACgkQhAOUmAZhnmpsnACfVn2QGUe4n8WlKsoKO52nWDjq
JoYAn1ciYTbeDsCEaNi2qyYyCcUjTzQ2
=PMRn
-----END PGP SIGNATURE-----

Daniel Krügler

unread,
Feb 6, 2012, 7:14:14 AM2/6/12
to
On 2012-02-06 09:56, Frank Birbacher wrote:
> Am 02.02.12 21:24, schrieb Daniel Krügler:
>> Some parts of your second overload look unnecessarily complicated
>> to me. Why shouldn't it be necessary to invoke the first overload
>> on function objects?
>
> Hmm, I'm sorry I don't quite understand this question. The combination
> of "why", "should not", and "is necessary" does not lead me to an
> interpretation that I can make sense of.

There was a thinko in my text above. The part "Why shouldn't it be
necessary" was supposed to be "Why should it be necessary"

>> Consider
>>
>> template<typename F> auto mkHolder(F&& f) -> typename
>> discover_f_type<decltype(&F::operator())>::function_type
>
> Hmm, you mean "mkHolder" shall not construct a "Holder" object anymore,
> but only a "boost::function?" This would mean the call will look like
>
> mkHolder(mkHolder([](...){...}))
> ?

My ideas were badly expressed. Let me be more precise here: I suggest to
separate your problem of building an Holder object out of a lambda
expression into two clear sub-problems:

1) Generate a std::function instance out of any lambda expression (or
anything that has an unambiguous operator() overload)
2) Generate your Holder object out of a function object or out of any
thing from (1)

Let's start with (1) and can define a nice general solution for this,
e.g. (replace std::function by boost::function)

#include <functional>
#include <type_traits>

template<class F>
struct deduce_function;

template<class Ret, class C, class... Args>
struct deduce_function<Ret (C::*)(Args...) const>
{
typedef std::function<Ret(Args...)> type;
};

template<class Ret, class C, class... Args>
struct deduce_function<Ret (C::*)(Args......) const>
{
typedef std::function<Ret(Args......)> type;
};

template<class Ret, class C, class... Args>
struct deduce_function<Ret (C::*)(Args...)>
{
typedef std::function<Ret(Args...)> type;
};

template<class Ret, class C, class... Args>
struct deduce_function<Ret (C::*)(Args......)>
{
typedef std::function<Ret(Args......)> type;
};

template<class F>
typename
deduce_function<decltype(&std::remove_reference<F>::type::operator())>::type
make_function(F&& f)
{
return typename
deduce_function<decltype(&std::remove_reference<F>::type::operator())>::type(f);
}

Now given this part, we can solve sub-part (2) much more easily:

#include <utility>

template<typename Arg, typename Result>
struct Holder {
typedef std::function<Result(Arg)> Func;
Func f;
Holder(Func&& newF) : f(std::move(newF)) {}
//...
};

template<typename F>
auto mkHolder(F&& f) -> decltype(make_function(std::declval<F>()))
{
return make_function(std::forward<F>(f));
}

From a user-pointer of view your additional overload for std::function
objects is unnecessary and both make_function as well as mkHolder should
work for such objects as well. Providing such an overload makes sense,
though, because there exists a lot of freedom for libraries to add
member function signatures due to sub-clause [member.functions].

>>> Is relying on&F::operator() fine for lambda types F?
>>
>> Good question. The current wording seems to specify this quite
>> precisely, so it looks like a reasonable approach to me.
>
> I wonder why there are no nested typedefs in a lambda closure type. I
> expected something like in std::unary_function. Well, I can see, that
> this is not a generic approach for any number of operator() argument
> types, but I expected at least a typedef for something. For example
> boost::function defines arg1 to argN. boost::variant defines an mpl
> sequence for its types. No agreement on any typedefs for lambdas?

Lambda closures are a pure core language thingee, in contrast to
std::function and similar types from <functional>. I don't think that
adding such typedefs in the core language is worth the effort this would
have for implementations.

HTH & Greetings from Bremen,

Daniel Krügler




Frank Birbacher

unread,
Feb 6, 2012, 7:00:50 PM2/6/12
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

Am 06.02.12 13:14, schrieb Daniel Krügler:
> There was a thinko in my text above.

;) Ok, I got it now.

> Let me be more precise here: I suggest to separate your problem of
> building an Holder object out of a lambda expression into two clear
> sub-problems:

Ok, this I understand and favour.

> Let's start with (1) and can define a nice general solution for
> this, e.g. (replace std::function by boost::function)
[snipped some code]

Yeah, great! This is definately clearer than everything I provided.
Thank you for the clear example.

> template<class Ret, class C, class... Args> struct
> deduce_function<Ret (C::*)(Args......) const> { typedef
> std::function<Ret(Args......)> type; };

Wow, what does that do? I haven't yet come across a double ellipsis. I
can think of template template parameters, but a concrete function
type cannot have such arguments, does it? Please provide an example
that invokes this specialization.

> Lambda closures are a pure core language thingee, in contrast to
> std::function and similar types from <functional>. I don't think
> that adding such typedefs in the core language is worth the effort
> this would have for implementations.

Without being rude: from a users point of view I don't care about the
distinction of core vs. library thingee. I was looking for some means
to help type deduction and I'd appreciated some predefined functions
to produce a std::function from a lambda or at least anything to aid
doing so.

I'm considering making the example complete and providing it to
Boost.Function. Would you agree on this?

Frank
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
Comment: GPGTools - http://gpgtools.org
Comment: keyserver x-hkp://pool.sks-keyservers.net

iEYEARECAAYFAk8wXFoACgkQhAOUmAZhnmqC5wCcCpziyS2XbP4qrxxK08A+X0B7
+NkAn1ShXslt0w/APA8mqxIEqgvJw9Xf
=IeLk
-----END PGP SIGNATURE-----

Daniel Krügler

unread,
Feb 7, 2012, 4:46:51 AM2/7/12
to
On 2012-02-07 01:00, Frank Birbacher wrote:
>> template<class Ret, class C, class... Args> struct
>> deduce_function<Ret (C::*)(Args......) const> { typedef
>> std::function<Ret(Args......)> type; };
>
> Wow, what does that do? I haven't yet come across a double ellipsis. I
> can think of template template parameters, but a concrete function
> type cannot have such arguments, does it? Please provide an example
> that invokes this specialization.

It just captures function types with ellipses, e.g. consider printf's
function type (modulo linkage) void(const char*, ...). Or expressed as a
lambda expression

[](const char*, ...) { }

But I was over-engineering here, std::function does not promise to hold
such entities, given it's specification to solely match

template<class R, class... ArgTypes>
class function<R(ArgTypes...)>;

> I'm considering making the example complete and providing it to
> Boost.Function. Would you agree on this?

Sure.

Greetings from Bremen,

Daniel Krügler


Reply all
Reply to author
Forward
0 new messages