How to deduce second parameter type from first parameter?

Viewed 322

I am new to template-metaprogramming. The second parameter will be the same as the function parameter passed. I want to deduce the second parameter type from Func.

template<typename Func>
void execute(Func func, decltype(Func) t) 
{
    std::cout << func(t) << std::endl;
}

int main() 
{
    std::function<int(float)> func1 = [](float f) { return int(f); };
    execute(func1,1.5);
    return 0;
}

This works, but I don't want to declare additional typenameme T since the info is already available in Func, so why not deduce.

template<typename Func, typename T>
void execute(Func func, T t) 
{
    std::cout << func(t) << std::endl;
}
5 Answers

In your example there's no need to know the exact argument types, so the easiest solution here is to use a variadic template. Just take a parameter pack and forward them:

template<typename Func, typename... Args>
void execute(Func func, Args&&... a) {
    std::cout << func(std::forward<Args>(a)...) << std::endl;
}

int main() {
    auto func1 = [](float f) { return int { f }; };
    execute(func1, 1.5);
    auto func2 = [](int i) { return float { i }; };
    execute(func2, 15);
    auto func3 = [](int a, int b, int c) { return a * b + c; };
    execute(func3, 3, 4, 5);
    return 0;
}

I want to deduce the second parameter type from Func

I don't want to declare additional typename T

I do not see any simple solution to your requirement, other than passing the callable object to the function, after binding with the argument.

Following will make sure your second requirement and not need change your original code that much.

#include <functional>
#include <iostream>

template<typename Func>
void execute(Func func) {
    std::cout << func() << std::endl;
}
int main() 
{
    auto func1 = std::bind([](float f) { return int(f); }, 2.5f);
    execute(func1);
    return 0;
}

Here is an example:

#include <tuple>
#include <type_traits>

template <typename> struct function_traits;

template <typename Function>
struct function_traits
    : public function_traits<decltype(
          &std::remove_reference<Function>::type::operator())> {};

template <typename ClassType, typename ReturnType, typename... Arguments>
struct function_traits<ReturnType (ClassType::*)(Arguments...) const>
    : function_traits<ReturnType (*)(Arguments...)> {};

/* support the non-const operator ()
 * this will work with user defined functors */
template <typename ClassType, typename ReturnType, typename... Arguments>
struct function_traits<ReturnType (ClassType::*)(Arguments...)>
    : function_traits<ReturnType (*)(Arguments...)> {};

template <typename ReturnType, typename... Arguments>
struct function_traits<ReturnType (*)(Arguments...)> {
  typedef ReturnType result_type;

  using argument_tuple = std::tuple<Arguments...>;
  template <std::size_t Index>
  using argument = typename std::tuple_element<Index, argument_tuple>::type;

  static const std::size_t arity = sizeof...(Arguments);
};

template <typename Function, std::size_t Index>
using nth_argument_type =
    typename function_traits<Function>::template argument<Index>;

#include <iostream>
using namespace std;

template <typename FN>
void execute(FN func, nth_argument_type<FN,0> arg0) {
  std::cout << func(arg0) << std::endl;
};

int main() {
  int i = 7;
  auto fn = [](int a) { return a * a; };
  auto fn2 = [](int a) mutable { return a * a; };
  execute(fn, 5);
  execute(fn, i);
  execute(fn2, 5);
  execute(fn2, i);
};

You should not do this. If you do, your example will stop working, because your function is int(float), but the argument is double.

#include <functional>
#include <iostream>

template<typename Arg>
void execute(std::function<int(Arg)> func, Arg t) {
    std::cout << func(t) << std::endl;
}

int main() {
    std::function<int(float)> func1 = [](float f) { return int(f); };
    execute(func1,1.5);
    return 0;
}

This won't compile. Live demo.

Stay with the T parameter, it is perfectly OK.

What you're looking for is a type trait that provides access to the types used in a function signature. There isn't such a type trait included in the standard library. So you'll either need to use a library that implements one, or implement it yourself.

If you can use boost, they have exactly this implemented in their type traits: https://www.boost.org/doc/libs/1_68_0/libs/type_traits/doc/html/boost_typetraits/reference/function_traits.html You would want to use the argN_type member to get the Nth argument's type.

If you can't use boost, you will need to implement your own type trait to make the argument types available. It might look like this:

// Base case for non-function types
template<typename T>
struct func_types { };

// Case for any generic function signature
template<typename Return, typename ...Args>
struct func_types<Return(Args...)>
{
    using ReturnType = Return;
    using ArgsTuple = std::tuple<Args...>;

    template<std::size_t N>
    struct args
    {
        using Type = std::tuple_element_t<N, ArgsTuple>;
    };
};

// Specialization for function pointers
template<typename Return, typename ...Args>
struct func_types<std::function<Return(Args...)>> : public func_types<Return(Args...)> { };

// Specialization for std::function
template<typename Return, typename ...Args>
struct func_types<std::function<Return(Args...)>> : public func_types<Return(Args...)> { };


// All further specializations for member functions,
// lambdas, etc. are left as an exercise to the reader

template<typename Func>
void execute(Func func, typename func_types<Func>::template args<0>::Type t)
{
    std::cout << func(t) << std::endl;
}

This example is basically a stripped down version of this blog post. This uses features added in C++14, so you would need to compile for that version, or newer.

Related