Reputation:
Is it possible to determine if the first argument of a callable object (function, lambda expression, functor, etc.) depends on a template parameter? For example: (using a hypothetical type trait)
#include <iostream>
template <typename T>
void f1(T x) {}
void f2(int x) {}
int main() {
auto f3 = [](auto x) {};
std::cout << std::boolalpha
<< is_first_arg_generic<decltype(f1)>::value << std::endl
<< is_first_arg_generic<decltype(f2)>::value << std::endl;
<< is_first_arg_generic<decltype(f3)>::value << std::endl;
}
Outputs:
true
false
true
Upvotes: 3
Views: 142
Reputation: 13306
Theoretically compiler could know it.
But no. f1
is a template, not a function, not even a type, so you cannot pass it to any template parameter list.
Though it is possible to determine a type of the first parameter of a function type. You could try to instantiate the template two times, and if the type of the first parameter differs between the two template instances, well then it is a templated parameter. It would, of course not work generally for every kind of template (i.e. in some templates the parameter may be same type for two different template parameters).
I made this for my use (there are probably better established variants out there — maybe something in Boost?):
#include <functional>
template< typename F >
struct function_traits;
template< typename Result, typename... Params >
struct function_traits< Result( Params... ) >{
static const size_t paramCount = sizeof...( Params );
using result = Result;
template< size_t i >
using param = typename std::tuple_element< i, std::tuple< Params... > >::type;
};
template< typename Result, typename... Params >
struct function_traits< Result(*)( Params... ) >
: public function_traits< Result( Params... ) > {
};
template< typename Result, typename... Params >
struct function_traits< std::function< Result( Params... ) > >
: public function_traits< Result( Params... ) >{
};
// shortcuts to help avoid the weird "typename" and "template" disambiguators
template< typename T >
using function_result_t = typename function_traits<T>::result;
template< typename T, size_t i >
using function_param_t = typename function_traits<T>::template param<i>; // lol, that's evil syntax
So for this case I would use it like:
using first_param_type_1st_try = function_param_t< f1<int>, 0 >;
using first_param_type_2nd_try = function_param_t< f1<unsigned>, 0 >;
bool is_first_param_probably_templated
= !std::is_same_v<first_param_type_1st_try, first_param_type_2nd_try>;
It is half pointless though, because you know trivially non-template function has non-template first parameter. And you have to instantiate any template name into a typename.
You could solve some of that using SFINAE and wrapping a function in a testing template. Again it would be ugly, and not generally functional.
Upvotes: 0
Reputation: 72431
No, I don't believe this is possible.
The function template f1
is very difficult to work with, because almost all possible uses of f1
in a type or expression must either immediately convert it to a specific type, selecting just one specialization of the template, or else immediately result in a compiler error. There's no way to just pass along the template name so it could be used in a SFINAE context, since template template parameters are only for class templates and alias templates, never function templates.
You would actually have the exact same problem with the name of an overloaded set of non-template functions.
But one thing that sort of gets closer to helping: Wrap the desired function template or overloaded functions in a generic lambda. Now it can at least be used as a template argument. And the problem is reduced to figuring out your f3
.
If a given type is somehow known or assumed to be a closure type, it's simple to detect whether it's the type of a generic lambda or not: the expression &F::operator()
is valid for a non-generic lambda and invalid for a generic lambda. Also, you can detect whether or not any callable type can or can't be called using a specific list of argument types, and/or whether the operator()
of a given callable class can be specialized by type deduction to result in a specific list of parameter types
But that's about as far as I think we can take it. There are a practically infinite number of possible argument/parameter type lists. If you can somehow restrict the possibilities you care about checking to a finite list, you could try them all. (Or if you can select a countable set of possibilities that doesn't rely on arbitrary identifiers, you could try as many as your compiler's maximum instantiation count allows.) But that doesn't really solve the problem. If multiple argument types are valid as the first argument, it could still be a non-template function whose first parameter type can implicitly convert from multiple types. Or even if you can determine only one first parameter type is ever valid, or that only one list of function parameters is valid, you could still have a function template whose first parameter is a dependent type which only ever evaluates to one specific type based on the possible template parameters deduced via other means.
Upvotes: 1