19

I'm not sure if it's possible, so that's what I want to find out.

I'd like to create a function which accepts any kind of functor/callable object, but I want to know what the argument type is. ( but not enforce it )

So, this one captures all but doesn't give me the type of the argument:

template < typename T >
void optionA( T );

This one captures most, and has the type of the argument

template < typename T >
void optionB( std::function< void(T) > );

But this one doesn't allow lambdas, so

optionB( [](int){} );

will not compile. Which is somewhat strange, as this will compile:

std::function< void(int) > func = [](int){};
optionB( func );

So is there a way to accept all options and also know which type of argument is expected?

thanks in advance!

-- edit --

The reason I'd like to do this comes from the fact that I want the user of my library to register a callback with a certain type. To me, the most natural way is

auto callback = []( int val ) { cout << "my callback " << val << endl; };
object.register( callback );

(with or without the use of callback as intermediate variable)

Since I need to modify the behaviour based on the type of value the user expects, I need to know what type he/she expects.

Arjan
  • 484
  • 3
  • 11
  • *"But this one doesn't allow lambdas"* Well it does allow lambdas, but it cannot deduce the `T`. So if you explicitly specify the `T`, it should work. It also doesn't work for function names and function pointers. – dyp Jan 30 '14 at 19:20
  • You can try to write some overloads: for function pointers, `std::function`s, and one that simply accepts a `T` (for lambdas / function objects). The last one doesn't let you deduce the parameter type however (e.g. generic lambdas), but you could try to find an `operator()` inside the `T` and deduce its parameter types (that's only possible if there's only one of them). – dyp Jan 30 '14 at 19:23
  • @dyp, Fair enough, but I feel that isn't good enough as this forces me to specify the argument 'twice', once on the option call and once in the lamba. I intend to use this in a library and I know I would be confused if this didn't 'just work'. So can we do better? – Arjan Jan 30 '14 at 19:25
  • [Here's](http://stackoverflow.com/a/4482680/420683) a boost version of how to deduce the parameter type(s) of a function object (with a single `operator()`). – dyp Jan 30 '14 at 19:28
  • 1
    `struct problem{templatevoid operator()(T&&t)const{std::cout<(t)<<"\n";}};` -- what type `T` should your `optionB` code deduce for this case? It works fine with `optionA`. Generic functors can represent entire overload sets of functions: your method presumes this is not true. In short, your `optionB` is fundamentally weaker than `optionA` even if you could get it to work. Often **what you are asking for is not required**, so: what problem are you actually trying to solve? There are *similar* questions that do not have this problem. Describe the real problem. – Yakk - Adam Nevraumont Jan 30 '14 at 20:06
  • Is there a set of types you are expecting to handle? Do you behave differently depending on which of the types you are passed? Is there an ordering of these types? What are you going to do with the type you deduce, exactly, and why do you want to do that? – Yakk - Adam Nevraumont Jan 30 '14 at 20:12
  • If you need to act depending on the argument type, expect an object that returns its type, and let the user of your library set the type of the object, no need for fancy template here, no? – xryl669 Apr 16 '14 at 14:22

2 Answers2

10

Here's an example that will work for most callables including functors and lambdas (although not for generic functors as @Yakk demonstrated in a comment on the question).

The code can also be useful when determining return type and multiple arguments.

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

template <typename C, typename Ret, typename... Args>
struct func_traits<Ret(C::*)(Args...) const> {
    using result_type =  Ret;

    template <std::size_t i>
    struct arg {
        using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
    };
};

template <typename T>
void option(T&& t) {
    using traits = func_traits<typename std::decay<T>::type>;

    using return_t = typename traits::result_type;         // Return type.
    using arg0_t = typename traits::template arg<0>::type; // First arg type.

    // Output types.
    std::cout << "Return type: " << typeid(return_t).name() << std::endl;
    std::cout << "Argument type: " << typeid(arg0_t).name() << std::endl;
}

To add support for regular functions add a specialization e.g.

template <typename Ret, typename... Args>
struct func_traits<Ret(*)(Args...)> { /* ... */ }

More useful info: Is it possible to figure out the parameter type and return type of a lambda?

Community
  • 1
  • 1
Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • I'm accepting this as answer, even though it has the same const/mutable/volatile modifier issues. As this method provides a generic way to detect argument types, it is therefore more usable imho. Is there any reason you write decltype(t) instead of T ? – Arjan Feb 01 '14 at 15:07
  • @Arjan You're right it should be just `T` instead of `decltype(t)`. The `std::decay` is still needed though as special rules apply to `T&&` when type deduction is going on (universal ref). – Felix Glas Feb 01 '14 at 16:48
  • It would be useful to have this `function_traits` template in the standard. Also a `make_function` to convert any callable to a function would be helpful. [Here is a proposal](https://groups.google.com/a/isocpp.org/d/msg/std-proposals/9zafJmVT2kQ/zzzXwD9DRM8J). – Philipp Feb 01 '14 at 17:31
5
template < typename T >
void option( function< void(T) > )
{
    cout << typeid( T ).name() << endl;
}

template < typename T >
void option( void (*func)(T) )
{
    option( function< void(T) >( func ) );
}

template< typename F, typename A >
void wrapper( F &f, void ( F::*func )( A ) const )
{
    option( function< void(A) >( bind( func, f, placeholders::_1 ) ) );
}

template< typename F, typename A >
void wrapper( F &f, void ( F::*func )( A ) )
{
    option( function< void(A) >( bind( func, f, placeholders::_1 ) ) );
}

template < typename T >
void option( T t )
{
    wrapper( t, &T::operator() );
}

void test( int )
{
}

struct Object
{
    void operator ()( float )
    {
    }
};

int main( int, char *[] )
{
    Object obj;

    option( test );
    option( [](double){} );
    option( obj );

    return 0;
}

Based on information found here c++0x: overloading on lambda arity, which I found through @dyps link

This isn't the best solution, since it requires overloads for const/non-const/volatile etc. It does get the job done in terms of the original problem I was trying to solve...

Community
  • 1
  • 1
Arjan
  • 484
  • 3
  • 11
  • I think you could implement the member function overloads via `std::is_member_function_pointer` and a generic decomposition trait. – dyp Jan 30 '14 at 22:39
  • The lambda btw selects the second overload for function pointers, because it can be converted to a function pointer, but only if it's stateless. I'd suggest adding a generic overload that takes just a `T` and then tries to detect an `operator()`. – dyp Jan 30 '14 at 22:41
  • o.O That's awful: you'll get combinations of cv-qualifiers and ref-qualifiers. Luckily, you only need to do this once: http://coliru.stacked-crooked.com/a/00750bf7564ab6d4 – dyp Jan 30 '14 at 23:33