Template deduction not working for function pointe

2019-06-06 10:32发布

问题:

I'm using detours and I find that the cast that they use very ugly, so I've written a couple of template functions to do the casting for me.

// Cast a function pointer to a void *
template <typename RET_TYPE, typename...ARGs>
void* fnPtrToVoidPtr(RET_TYPE(WINAPI * pOriginalFunction)(ARGs...))
{
    return (void*)pOriginalFunction;
}

// Cast a function pointer that is referencable to a void *&
template <typename RET_TYPE, typename...ARGs>
void*& fnPtrRefToVoidPtrRef(RET_TYPE(WINAPI*& pOriginalFunction)(ARGs...))
{
    return (void*&)pOriginalFunction;
}

This allows me to do the following call:

BOOL (WINAPI *pDestroyIcon)(HICON) = DestroyIcon;
DetourAttach(&fnPtrRefToVoidPtrRef(pDestroyIcon), fnPtrToVoidPtr(DestroyIcon));

However, I was wondering if I could consolidate the two function names fnPtrRefToVoidPtrRef and fnPtrToVoidPtr into one name.

Doing the following doesn't work as it can't deduce the template arguments:

// Cast a function pointer to a void *
template <typename RET_TYPE, typename...ARGs>
void* fnPtrToVoidPtr(RET_TYPE(WINAPI * & pOriginalFunction)(ARGs...))
{
    return (void*)pOriginalFunction;
}

// Cast a function pointer that is referencable to a void *&
template <typename RET_TYPE, typename...ARGs>
void*& fnPtrToVoidPtr(RET_TYPE(WINAPI * && pOriginalFunction)(ARGs...))
{
    return (void*&)pOriginalFunction;
}

BOOL (WINAPI *pDestroyIcon)(HICON) = DestroyIcon;
void* p1 = fnPtrToVoidPtr(DestroyIcon);
void** p2 = &fnPtrToVoidPtr(pDestroyIcon);

causing the following error:

// error C2784: 'void *&fnPtrToVoidPtr(RET_TYPE (__stdcall *&&)(ARGs...))' : could not deduce template argument for 'overloaded function type' from 'overloaded function type'

Using my original functions, this works fine:

    BOOL (WINAPI *pDestroyIcon)(HICON) = DestroyIcon;
    void* p1 = fnPtrToVoidPtr(DestroyIcon);
    void** p2 = &fnPtrRefToVoidPtrRef(pDestroyIcon);

However, if I change fnPtrRefToVoidPtrRef to this:

// Cast a function pointer that is referencable to a void *&
template <typename RET_TYPE, typename...ARGs>
void*& fnPtrRefToVoidPtrRef(RET_TYPE(WINAPI*&& pOriginalFunction)(ARGs...))
{
    return (void*&)pOriginalFunction;
}

I get the following error:

error C2664: 'void *&fnPtrRefToVoidPtrRef<BOOL,HICON>(RET_TYPE (__stdcall *&&)(HICON))' : cannot convert argument 1 from 'BOOL (__stdcall *)(HICON)' to 'BOOL (__stdcall *&&)(HICON)'

Which seems to be why it can't do template deduction, it doesn't recognize it to be the same (or convertible?) type. Is there a way to get C++ to properly deduce the function pointer?

回答1:

There are two problems with your code. Let's fix them in order.

First, the bodies and return types of the two fnPtrToVoidPtr overloads have to be switched. What you want is to convert lvalues of function pointer type to lvalues of type void*, through the (void*&) cast, so this cast should go in the body of the function taking a * & - this is the parameter type that will bind to modifiable lvalues, the other one, * && will bind to rvalues. Conversely, the (void*) cast has to go into the function taking a * &&. Obviously, the return types need to be changed accordingly.

Now, the reason for the deduction failure: when making a call like fnPtrToVoidPtr(DestroyIcon) in your original version, you're relying on the function-to-pointer conversion. This conversion doesn't happen if the parameter (destination) is a reference.

So, in your second version, where both overloads take references, the parameters are references to pointers, but the argument is a function identifier; template arguments for the former cannot be deduced from the latter, so deduction fails. The simplest fix for this one is to explicitly provide a function pointer for the call, like this: fnPtrToVoidPtr(&DestroyIcon).

&DestroyIcon is an rvalue, so the * && parameter will bind to it, * & won't, exactly what we want.

With these two fixes, a compilable version of your code becomes:

#include "windows.h"

// Cast a function pointer to a void *
template <typename RET_TYPE, typename...ARGs>
void* fnPtrToVoidPtr(RET_TYPE(WINAPI * && pOriginalFunction)(ARGs...))
{
   return (void*)pOriginalFunction;
}

// Cast a function pointer that is referencable to a void *&
template <typename RET_TYPE, typename...ARGs>
void*& fnPtrToVoidPtr(RET_TYPE(WINAPI * & pOriginalFunction)(ARGs...))
{
   return (void*&)pOriginalFunction;
}

BOOL(WINAPI *pDestroyIcon)(HICON) = DestroyIcon;

int main()
{
   void* p1 = fnPtrToVoidPtr(&DestroyIcon);
   void** p2 = &fnPtrToVoidPtr(pDestroyIcon);
}

If you don't like having to use the & operator in front of function names, you can also change the overload taking a * && to take a & - an lvalue reference to a function. Now, that version can be called as fnPtrToVoidPtr(DestroyIcon). A && (rvalue reference to function) will work too, since rvalue references to functions bind to function lvalues as well (all identifier expressions designating functions are lvalues).