Home > Mobile >  C function's variadic template deduction fails with MSVC, succeeds with Clang
C function's variadic template deduction fails with MSVC, succeeds with Clang

Time:01-07

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

  • Related