Given a function passed as a template argument to a function, how can I determine the type of the of it's first parameter?
For example:
template<auto Func>
void run_func(/* type of argument???? */ value) {
Func(value);
}
This particular use-case requires that the Func is a compile time constant. If possible I'd prefer to exact the type from Func
rather than add another template argument as I wish to be able to refer to the function like so:
auto function_runner = &run_func<&func>;
Thanks
The type of the function is known at compile-time and can be obtained using std::remove_pointer_t<decltype(Func)>
.
You would have to write a type trait that gives you information about the parameters like parameters<std::remove_pointer_t<decltype(Func)>>::type
; see Get types of C++ function parameters
This looks something like:
template<typename Sig>
struct parameters;
// extremely imperfect solution because it doesn't work for noexcept,
// const, volatile, reference-qualified, or variadic functions
template<typename R, typename ...Args>
struct parameters<R(Args...)>
{
using type = std::tuple<Args...>;
};
// example is std::tuple<int, float>;
using example = parameters<void(int, float)>::type;
Making a proper solution is non-trivial though because there are 48 partial specializations you'd need to write to cover every kind of function, not just ones that have no cv or reference qualifier, and are not noexcept
, like in the example.
In most cases, you don't really need to know the types of parameters (or the first parameter). Maybe you can simply ask
Can I invoke this function with
int
?
Rather than:
What is the type of the first parameter? Make sure that it's
int
.
The former question can be answered easily with std::is_invocable
.
You can then use C++26 pack indexing, std::tuple_element
, or some other means to get the first type in the tuple.
The final solution then looks like:
// based on std::invocable test
template<auto Func, typename T>
requires std::invocable<decltype(Func), T&&>
void run_func(T&& value) {
Func(std::forward<T>(value));
}
// based on extracting the first parameter type
template <typename T>
using function_pointer_first_parameter_t
= std::tuple_element_t<0, std::remove_pointer_t<Func>>;
template<auto Func>
void run_func(function_pointer_first_parameter_t<decltype(Func)> value) {
Func(std::forward<decltype(value)>(value));
}
A glaring problem with the latter solution is that you're unnecessarily creating intermediate objects.
For example, if the parameter type is std::string
, you're unnecessarily calling the move constructor to pass it down to Func
, since value
is a separate object. The caller might also have its own std::string
object on top of that, so there are three separate objects in total.
Furthermore, if you have a parameter which is say, a string literal (of type const char[N]
) and if Func
takes std::string
by value, then you're also missing the opportunity to simply call the std::string(const char*)
constructor rather than creating an unnecessary temporary std::string
object.
Arguably, this is why the latter solution is strictly worse than the former solution, even though it has one less template parameter and may appear more elegant at first glance.