Makogan
Makogan

Reputation: 9538

Extract type of input parameter in templated function taking a function as parameter

I have this function:

template<typename T, int (*F)(T&)>
void DoStuff(T& s)
{
    auto an = make_any<T>(s);
    cout << _GetDataFromAny<MyStruct, F>(an);
}

Which needs to be called like this:

DoStuff<MyStruct, Fun>(s);

This works fine, however I don't like having to specify the first type, since it is part of the signature of the second parameter. I would like to be able to deduce it such that I can just call:

DoStuff<Fun>(s);

However, I don't know how to specify in the template that the type T needs to be deduced from the signature of the function F.

Is this possible?

Upvotes: 2

Views: 101

Answers (2)

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122460

Disclaimer: This answer went through a series of edits and corrections, many thanks goes to Jarod42 for his patience and help, and cigien for fruitful discussion. It is a bit longer than necessary, but I felt it is worth to keep a bit of the history. It is: the most simple / the one I would prefer / some explanation of the previous confusion. For a quick answer, read the second part.


The simple

You can use an auto template parameter (since C++17) for the function pointer and let T be deduced from the argument:

template<auto F, typename T>
void DoStuff(T& s)
{
    int x = F(s);
}

int foo(double&){ return 42;}

int main() {
    double x;
    DoStuff<&foo>(x);
}

Live Demo


The "right"

The downside of the above is that F and T are "independent". You can call DoStuff<&foo>(y) with eg a std::string y; and instantiation will only fail when calling F. This might lead to a unnecessarily complex error message, depending on what you actually do with F and s. To trigger the error already at the call site when a wrong T is passed to a DoStuff<F> you can use a trait to deduce the argument type of F and directly use that as argument type of DoStuff:

template <typename T> struct param_type;

template <typename R,typename P>
struct param_type< R(*)(P&)> {
    using type = P;
};
    
template<auto F>
void DoStuff(typename param_type<decltype(F)>::type& s)
{
    int x = F(s);  // (1)
}

int foo(double&){ return 42;}    

int main() {
    double x;
    DoStuff<foo>(x);
    std::string y; 
    DoStuff<foo>(y);  // (2) error 
}

Now the error that before would only happen inside the template (1) happens already in main (2) and the error message is much cleaner.

Live Demo


The "wrong"

Mainly as curiosity, consider this way of deducing the parameter type from the function pointer:

template <typename T> struct param_type;

template <typename R,typename P>
struct param_type< R(*)(P&)> {
    using type = P;
};
    
template<auto F, typename T = typename param_type<decltype(F)>::type>
void DoStuffX(T& s)
{
}

int foo(double&){ return 42;}    

int main() {
    double x;
    DoStuffX<foo>(x);
}

This was my original answer, but it was not actually doing what I thought it was doing. Note that I was not actually calling F in DoStuff and to my surprise this compiled:

int main() {
    std::string x;
    DoStuffX<foo>(x);
}

The reason is that the default template argument is not used when T can be decuded from the passed parameter (see here). That is, DoStuffX<foo>(x); actually instantiates DoStuffX<foo,std::string>. We can still get our hands on the default via:

int main() {
    std::string x;
    auto f_ptr = &DoStuffX<foo>;
    f_ptr(x);  // error
}

Now calling DoStuffX<foo> with a std::string is a compiler error, because here DoStuffX<foo> is instantiated as DoStuffX<foo,double>, the default argument is used (there is no parameter that could be used to deduce T when DoStuffX is instantiated).

Upvotes: 3

cigien
cigien

Reputation: 60218

You can write a helper that deduces the argument type of a function pointer that returns int:

template<typename T>
T arg_type(int(*)(T&));

and then rewrite your function template slightly to take in a function pointer as a non-type template parameter, and figure out the argument type from that

template<auto F, typename T = decltype(arg_type(F))>
void DoStuff(T& s) {
 // ...
}

Upvotes: 4

Related Questions