2

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?

2 Answers 2

4

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) {
 // ...
}
Sign up to request clarification or add additional context in comments.

24 Comments

I got into the habit of only reloading when I finished writing, its easier to see that another answer is better than mine rather than trying to compete :P
Well, this was fast! I can't yet accept the answer (SO won't let me).
@largest_prime_is_463035818 Yeah, I know what you mean. Still, it's not competing really; your solution is equally valid, and it would be fine to post it.
sure, competition it is not, I was joking. I just dont see what mine would add. I could try to get it without the auto parameter, but its getting late here
Notice that = decltype(arg_type(F)) is rarely useful, as T is deduced. (useful for auto func = &DoStuff<&Fun>;)
|
3

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).

2 Comments

template<auto F> void DoStuffY(typename param_type<decltype(F)>::type& s) is an alternative. No deduction at all. function pointer or regular call possible (only with expected type).
@Jarod42 That should actually be the answer. Must be something about the question, somehow I forgot everything I know and more. I will edit

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.