The following is a condensed (and slightly contrived) reproducible example of something else I'm trying to do to create a real-world convenience function. Basically, I'd like to be able to simplify function calls to functions with a particular signature, but which may have other, arbitrary preceding arguments before the final, conventional arguments.
Consider a function that returns a result via an int&
as its final argument. For example:
void SimpleValue(int &output)
{
output = 42;
}
void AddTwoValues(int a, int b, int &output)
{
output = a b;
}
Functions of a similar nature could take any arguments before the final output
, and use them to compute the output. They should always contain the final output argument, and should always (for the purposes of this example) return void
.
I can create a C templated alias for functions of this type:
template <typename... ARGS>
using FuncType = void (*)(ARGS... args, int &output);
Then if I wanted to write a convenience function to print the output of any of this family of functions, I could write:
// Ignoring forwarding for the sake of clarity.
// I've also tested using std::forward and it
// does not affect the context of this question.
template <typename... ARGS>
void Print(FuncType<ARGS...> func, ARGS... args)
{
int output = 0;
func(args..., output);
std::cout << "Computed output: " << output << std::endl;
}
The problem comes when calling Print()
. Using Clang on Replit, I can write the following:
Print(&SimpleValue);
Print(&AddTwoValues, 3, 4)
And get the expected output:
Computed output: 42
Computed output: 7
However, MSVC on Windows does not compile this code. I get the error:
error C2784: 'void Print(void (__cdecl *)(ARGS...,int &),ARGS...)': could not deduce template argument
for 'void (__cdecl *)(ARGS...,int &)'
from 'void (__cdecl *)(int,int,int &)'
I don't often consider myself smarter than a compiler, but given that template pattern and signature, even I could deduce those arguments, and so apparently can Clang.
If I manually supply the arguments, MSVC does accept the second function call:
Print<int,int>(&AddTwoValues, 3, 4)
However, to get the first one to work, I have to define an overload of Print()
for functions which do not take any arguments before output
.
Is this a bug with MSVC? I don't understand why it can't deduce the arguments properly under these circumstances.
CodePudding user response:
Independently of which compiler is correct, there is no point in having FuncType
. You can simply accept any type as the callable. Then it doesn't even have to be a function pointer:
template <typename F, typename... ARGS>
void Print(F func, ARGS... args)
{
int output = 0;
func(args..., output);
std::cout << "Computed output: " << output << std::endl;
}
And if you want to make sure that the function will not be chosen in overload resolution if func
can't actually be called this way, then you can add a constraint (assuming C 20 here):
template <typename F, typename... ARGS>
void Print(F func, ARGS... args)
requires requires { func(args..., std::decltype<int&>()); }
{
int output = 0;
func(args..., output);
std::cout << "Computed output: " << output << std::endl;
}
(If you like you can also add perfect-forwarding by replacing ARGS...
with ARGS&&...
and args...
with std::forward<ARGS>(args)...
everywhere. The same can be done with F
and func
. Instead of a direct function call you could also use std::invoke
to make it work even more generally, e.g. with member function pointers.)