Reputation: 16168
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
Upvotes: 3
Views: 88
Reputation: 968
If you're able to use a 3rd-party (effectively single header-only) function traits library (free, open-source, production-grade, fully documented), then see here (I'm the author). You can easily do it like so (but heed the caveats pointed out in Jan Schultke's own solution and there may be others depending on what your function's 1st arg type is - this library handles all specializations Jan referred to and more, but like so many things in C++, if misapplied it won't stop you from blowing your leg off as Stroustrup himself is reported to have once said - always exercise caution :):
Click here to run
// Standard C++ headers
#include <iostream>
///////////////////////////////////////////////////////
// Header for the "FunctionTraits" library (the only
// header you ever need to #include - library is
// effectively single-header only). See
// https://github.com/HexadigmSystems/FunctionTraits
///////////////////////////////////////////////////////
#include "TypeTraits.h"
// Everything in above header declared in this namespace
using namespace StdExt;
////////////////////////////////////////////////////////
// Wrapper for the library's "ArgType_t" template used
// to retrieve the (zero-based) "Ith" arg of any C++
// function type (just pass "I" as the 2nd arg). See:
// https://github.com/HexadigmSystems/FunctionTraits#argtype_t
// The library effectively handles any function type
// including (free or non-static member) function
// pointers, references to function pointers,
// non-overloaded functors (including lambdas), etc.
// Passing zero (0) for "I" here to retrieve the type
// of the 1st arg of "Func" (again, "I" is zero-based).
////////////////////////////////////////////////////////
template <auto Func>
using FirstArg_t = ArgType_t<decltype(Func), 0>;
template<auto Func>
void run_func(FirstArg_t<Func> value)
{
//////////////////////////////////////////////
// Displays the type of "value" which is the
// type of the 1st arg of "Func so literally
// displays "double" in this example (1st arg
// of "TestFunc" below). Note that "tcout"
// below (from this library) always resolves
// to "std::cout" on non-MSFT platforms and
// on MSFT platforms to "std::wcout if
// _UNICODE or UNICODE is #defined (normally
// the case) or std::cout otherwise (very
// rare anymore). See "TypeName_v" in the
// library's docs:
// https://github.com/HexadigmSystems/FunctionTraits#typename_v
//////////////////////////////////////////////
tcout << TypeName_v<decltype(value)>;
}
void TestFunc(double)
{
// ...
};
int main()
{
// Passing a double (type of 1st arg of "TestFunc")
run_func<TestFunc>(0.5);
return 0;
}
Upvotes: 1
Reputation: 40164
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.
Upvotes: 4
Reputation: 16168
Okay, I've found a solution, maybe someone has a better one:
template<typename T> struct decode { };
template<typename T> struct decode< void(*)(T) > {
using type = T;
};
template<auto Func>
void run_func(typename decode<decltype(Func)>::type value) {
Func(value);
}
Upvotes: 0