Search code examples
c++templatesclanginteger-promotiontype-promotion

How to force type promotion in a variadic function wrapper?


I am writing a wrapper to call API functions in a vtable. This is done through a variadic temple, to wrap API functions with an arbitrary number of parameters. I have found that some calls work only if the numbers passed as arguments are forced to be long long:

vtablecall(pIntf, methodnumber, TRUE, 0, 0); //CRASHES

vtablecall(pIntf, methodnumber, (long long)TRUE, (long long)0, (long long)0); //WORKS

The problem is that the compiler does not enforce (long long) by itself. Debugging shows that the TRUE or 0 arguments are considered int. Then maybe they are somehow optimized by the compiler (my suspicion) before being sent to the pointed function.

My goal: what syntax use in the template so that all integer arguments are promoted to long long (and if possible all real numbers promoted to double)? Putting (long long) every time isn't a good solution because of the risk of mistake, and initializer_list isn't an option because some parameters may be real numbers.

Thanks!

template <typename... pack> HRESULT WINAPI vtablecall(IUnknown* pIntf, const int methodnumber, pack... args){
HRESULT WINAPI (*psub)(IUnknown*, pack...)=(HRESULT WINAPI (*)(IUnknown*, pack...))((LPVOID**)pIntf)[0][methodnumber];
    //// breakpoint here
    return psub(pIntf, std::forward<pack>(args)...);
}

int main() {
    IUnknown* pIntf=...
    int mymethodnumber=43;
    vtablecall(pIntf, mymethodnumber, pIntf, methodnumber, TRUE, 0, 0);
}

---> CRASHES: Process exited with status = 0x80131506

Frame variables in debugger before calling 'psub':

(IUnknown *) pIntf = 0x00000272be28ffa0
(const int) methodnumber = 43
(int) args = 1
(int) args = 0
(int) args = 0
(HRESULT (*)(IUnknown *, int, int, int)) psub = 0x000002aee04146ba

Now, with setting explicitly arguments to long long:

int main() {
    IUnknown* pIntf=...
    int mymethodnumber=43;
    vtablecall(pIntf, mymethodnumber, pIntf, methodnumber, (long long)TRUE, (long long)0, (long long)0);
}

---> works OK

Frame variables in debugger before calling 'psub':

(IUnknown *) pIntf = 0x0000024bd525ffa0
(const int) methodnumber = 43
(long long) args = 1
(long long) args = 0
(long long) args = 0
(HRESULT (*)(IUnknown *, long long, long long, long long)) psub = 0x0000024bd57e46ba

Solution

  • You can use std::conditional to define your own type

    template<typename T>
    using my_type_t = std::conditional_t<std::is_integral_v<T>, long long, T>;
    

    If T is an integer of any type, then my_type_t<T> will be long long, otherwise it will be T.

    So you can use it as such

    ...
    HRESULT WINAPI (*psub)(IUnknown*, my_type_t<pack>...) = ...