19

Consider the following function:

template<class F>
void register_handler( F& f ) // any callable object
{
   // find out T - the argument type of f
}

Here f is some callable object, accepting one argument. It may be a function pointer, an std::function or a result of std::bind.

The problem is, how to determine the argument type of f and do some actions based on that type?


An easy workaround would be to add the type to template explicitly, like

template<class T, class F> // T is the argument type of F
void register_handler( F& f )

but this seems an overkill because type F should already contain the necessary information about type T.

Grigor Gevorgyan
  • 6,753
  • 4
  • 35
  • 64
  • 1
    What are you using the information for? As detailed below, your problem cannot be solved in general, but the problem that motivated it may be solvable. – Yakk - Adam Nevraumont Mar 25 '14 at 12:34
  • Please describe your problem and not your solution. "get the argument type `f`!" is the solution to which current problem you have? – R. Martinho Fernandes Apr 01 '14 at 11:40
  • 1
    If it helps, "because type F should already contain the necessary information about type T." is an invalid assumption. – R. Martinho Fernandes Apr 01 '14 at 11:44
  • You can specialize templates to fit your needs. I cannot remember the exact syntax and the exact practice, but you can have a look at : [Template specialization](http://en.cppreference.com/w/cpp/language/template_specialization) Hope this helps :) Edit : I found a better link – johnkork Mar 25 '14 at 10:19
  • A good reference implementation can be found in RapidCheck, especially [here](https://github.com/emil-e/rapidcheck/blob/master/include/rapidcheck/detail/FunctionTraits.h) Note that this implementation can only deal with implemented argument types (most primitive types are implemented). – Unapiedra Oct 08 '17 at 22:42

4 Answers4

19

Assuming F is any callable type, you cannot get its argument type. Consider this:

struct callable
{
    void operator() (int);
    void operator() (float *);
    void operator() (std::string const &);
    void operator() (std::list<int> &);
};

the type of argument is an ambiguity here.

keyser
  • 18,829
  • 16
  • 59
  • 101
lisyarus
  • 15,025
  • 3
  • 43
  • 68
  • 1
    Even worse in c++1y with a generic lambda `[](auto v) {}` – galop1n Mar 25 '14 at 10:38
  • @lisyarus you could define one as the default. When using sfinae this would be the prefered overload if more than one possibility is found. – David Feurle Mar 25 '14 at 11:41
  • 4
    @galop1n: generic lambdas are just a sugar for `template operator()`, so the same problem exists in c++11 and c++03. – lisyarus Mar 25 '14 at 14:10
  • 1
    @DavidFeurle: to select a default overload I should get the list of possible overloads, which is impossible. – lisyarus Mar 25 '14 at 14:11
  • @lisyarus you could define the possible overloads beforehand and define a default overload. I don't see nothing impossible here. – David Feurle Mar 26 '14 at 07:28
  • @DavidFeurle: what do you mean saying "beforehand"? I would be happy if you provided some examples of code) – lisyarus Mar 26 '14 at 16:03
  • 3
    If you're going to define possible signatures beforehand you can just test if the type is callable with those signatures and ignore this "get the argument type" nonsense. – R. Martinho Fernandes Apr 01 '14 at 11:45
  • It's a strange argument. In the same manner you can argue: there can be void f(double), void f(int), void f(std::string), therefore it is impossible to get the type of the first argument. Yet we have boost::function_traits::arg1_type, which is perfectly working counteracting your argumentation. Yes, it doesn't work with overloaded functions. So what. There is a plenty of stuff that doesn't. Try bind for example. – facetus Jun 22 '16 at 01:11
  • @noxmetus Yes, one can ask the same question about free functions, and the answer stays the same: it is ***impossible*** in general. Of course, one can say the he doesn't care at all about overloaded functions, - and, once this is *explicitly stated*, `boost::arg1_type` provides the answer. Overloading is a very important and usefull mechanism, and implicitly ignoring it sounds terrible. – lisyarus Apr 26 '17 at 11:51
  • @lisyarus It's possible in some cases. Therefore the statement it's impossible at all is false. – facetus Apr 30 '17 at 18:43
6

This blogpost shows how to implement some function type traits. These should work with everything callable (exception: polymorphic functors :P). You could iterate over the arguments, and use their type to do some sfinae or as a additional template argument.

Function traits as copied from blogpost:

#include <tuple>

// as seen on http://functionalcpp.wordpress.com/2013/08/05/function-traits/
template<class F>
struct function_traits;

// function pointer
template<class R, class... Args>
struct function_traits<R(*)(Args...)> : public function_traits<R(Args...)>
{};

template<class R, class... Args>
struct function_traits<R(Args...)>
{
    using return_type = R;

    static constexpr std::size_t arity = sizeof...(Args);

    template <std::size_t N>
    struct argument
    {
        static_assert(N < arity, "error: invalid parameter index.");
        using type = typename std::tuple_element<N,std::tuple<Args...>>::type;
    };
};

// member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...)> : public function_traits<R(C&,Args...)>
{};

// const member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...) const> : public function_traits<R(C&,Args...)>
{};

// member object pointer
template<class C, class R>
struct function_traits<R(C::*)> : public function_traits<R(C&)>
{};

// functor
template<class F>
struct function_traits
{
    private:
        using call_type = function_traits<decltype(&F::operator())>;
    public:
        using return_type = typename call_type::return_type;

        static constexpr std::size_t arity = call_type::arity - 1;

        template <std::size_t N>
        struct argument
        {
            static_assert(N < arity, "error: invalid parameter index.");
            using type = typename call_type::template argument<N+1>::type;
        };
};

template<class F>
struct function_traits<F&> : public function_traits<F>
{};

template<class F>
struct function_traits<F&&> : public function_traits<F>
{};

Testcode:

#include <iostream>

class A
{
};

template <class T>
struct Functor
{
  void operator()(const T& t)
  {}
};

struct Register
{
  //int parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_same<typename function_traits<T>::template argument<0>::type, const int&>::value>::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }

  //A parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_same<typename function_traits<T>::template argument<0>::type, const A&>::value>::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }
};

void intFunc(const int&) {}
void aFunc(const A&){}

int main(int /*argc*/, char */*argv*/[])
{
  Functor<int> intFunctor;
  Functor<A> aFunctor;

  Register::RegisterFunctor(intFunctor);
  Register::RegisterFunctor(&intFunc);
  Register::RegisterFunctor(aFunctor);
  Register::RegisterFunctor(&aFunc);
  return 0;
}
David Feurle
  • 2,687
  • 22
  • 38
  • 1
    This is unnecessarily limiting and won't work in general; the limitations are particularly problematic with adoption of polymorphic lambdas and polymorphic callable objects in Boost or the standard library. – R. Martinho Fernandes Apr 01 '14 at 11:43
  • @R.MartinhoFernandes this is correct. This solution does not adress polimorphic lambdas. In special cases with sfinae it could be done since you could test if the lambda supports beeing called with a parameter of a certain type -> see my other answer – David Feurle Apr 01 '14 at 12:36
  • 2
    I think the general problem is we do not know exactly what the purpose of the type parameter is. If it is used to do sfinae it is possible to test if the functor supports beeing called. If it is for other purposes it can not work with polymorphic functors. But the use case may be to support only non polymorphic functors. – David Feurle Apr 01 '14 at 12:39
0

if F is a std::functionyou should be able to use the its member type and check with `std::is_same':

template<class F>
void register_handler( F& f ) // any callable object
{
   // find out T - the argument type of f
   if(std::is_same<int, F::argument_type>::value)
   { .... }
   //etc .....

}

An up and running example here

but that kind of code can quickly become a mess to maintain.

alexbuisson
  • 7,699
  • 3
  • 31
  • 44
  • 1
    Just be aware that `argument_type` is deprecated in C++17 and removed in C++20: https://stackoverflow.com/q/52556432 – pooya13 Jun 13 '21 at 20:05
0

You could use sfinae and test if your argument is convertible to a std::function with the required arguments:

#include <type_traits>
#include <functional>
#include <iostream>

class A
{
};

template <class T>
struct Functor
{
  void operator()(const T& t)
  {}
};

struct Register
{
  //int parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_constructible<typename std::function<void (int)>, T>::value >::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }

  //A parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_constructible<typename std::function<void (A)>, T>::value >::type* = 0)
  {
    std::cout << "Register a func" << std::endl;
  }
};

void intFunc(int) {}
void aFunc(A){}

int main(int /*argc*/, char */*argv*/[])
{
  Functor<int> intFunctor;
  Functor<A> aFunctor;

  Register::RegisterFunctor(intFunctor);
  Register::RegisterFunctor(&intFunc);
  Register::RegisterFunctor(aFunctor);
  Register::RegisterFunctor(&aFunc);
  return 0;
}
David Feurle
  • 2,687
  • 22
  • 38