12

I'm trying to wrap the Windows API functions to check errors when I so choose. As I found out in a previous SO question, I could use a template function to call the API function, and then call GetLastError() to retrieve any error it might have set. I could then pass this error to my Error class to let me know about it.

Here's the code for the template function:

template<typename TRet, typename... TArgs>
TRet Wrap(TRet(WINAPI *api)(TArgs...), TArgs... args)
{
    TRet ret = api(args...);
    //check for errors
    return ret;
}

Using this I can have code as follows

int WINAPI someFunc (int param1, BOOL param2); //body not accessible

int main()
{
    int ret = someFunc (5, true); //works normally
    int ret2 = Wrap (someFunc, 5, true); //same as above, but I'll get a message if there's an error
}

This works wonderfully. However, there is one possible problem. Take the function

void WINAPI someFunc();

When subbing this into the template function, it looks as follows:

void Wrap(void(WINAPI *api)())
{
    void ret = api(); //<-- ahem! Can't declare a variable of type void...
    //check for errors
    return ret; //<-- Can't return a value for void either
}

To get around this, I tried creating a version of the template where I replaced TRet with void. Unfortunately, this actually just causes an ambiguity of which one to use.

That aside, I tried using

if (strcmp (typeid (TRet).name(), "v") != 0) //typeid(void).name() == "v"
{
    //do stuff with variable to return
}

else
{
    //do stuff without returning anything
}

However, typeid is a runtime comparison, so the code still doesn't compile due to trying to declare a void variable, even if it never will.

Next, I tried using std::is_same <TRet, void>::value instead of typeid, but found out that it was a runtime comparison as well.

At this point, I don't know what to try next. Is there any possibility of getting the compiler to believe that I know what I'm doing will run fine? I don't mind attaching an extra argument to Wrap, but I couldn't really get anything out of that either.

I use Code::Blocks with GNU G++ 4.6.1, and Windows XP, as well as Windows 7. Thanks for any help, even if it is telling me that I'll have to end up just not using Wrap for functions that return void.

Nathan Kleyn
  • 5,103
  • 3
  • 32
  • 49
chris
  • 60,560
  • 13
  • 143
  • 205
  • 1
    I understanding why specializing the return type as void didn't work (can't disambiguate on return types) but specializing with void **and** adding an additional parameter should work, what happened? You may have to invoke with explicit types. – Tod Mar 08 '12 at 22:11
  • 1
    Interesting question. Side note: in addition to not being statically checked, the `typeid` solution is also not portable. The standard makes no guarantees about the string returned by `type_info::name()`. – Thomas Mar 08 '12 at 22:14
  • I didn't think of combining the two. I'll try it out. Thanks for the heads-up on the typeid issue too. – chris Mar 08 '12 at 22:14
  • Your explicit types point proved to do the trick. All I have to do when calling a function returning void is `Wrap (someFunc, arg1, arg2...)`. The DummyType can be replaced with anything (except void) for a void return, and can be left out, or replaced with the actual return type for a different return type. If you make it an answer I'll accept it, thanks :) – chris Mar 08 '12 at 22:25
  • Your use of GetLastError is incorrect. It only returns meaningful values if the latest call to an API function failed. It can return non-zero even if the latest API call succeeded. You have to check the API function's return value for failure before calling GetLastError. – David Heffernan May 15 '12 at 07:29
  • @DavidHeffernan, Yes, I've since realized that to do this I'd need some way to handle every type of Windows error a function could use, and that unless I hardcode all of the functions in, the user still has to look at how to handle failure in order to choose the right one. – chris May 15 '12 at 07:33

3 Answers3

10

You can use a helper class to fine tune specializations:

template <typename F>
struct wrapper
{};

template <typename Res, typename... Args>
struct wrapper<Res(Args...)>
{
    static Res wrap(Res (WINAPI *f)(Args...), Args&& args...)
    {
        Res r = f(std::forward<Args>(args)...);
        // Blah blah
        return r;
    }
};

template <typename... Args>
struct wrapper<void(Args...)>
{
    static void wrap(void (WINAPI *f)(Args...), Args&& args...)
    {
        f(std::forward<Args>(args)...);
        // Blah blah
    }
};

Now, you can write the wrapper:

template <typename Res, typename... Args>
Res Wrap(Res (WINAPI *f)(Args...), Args&& args...)
{
    return wrapper<Res(Args...)>::wrap(f, std::forward<Args>(args)...);
}

Note that it works even when Res is void. You're allowed to return an expression returning void in a function returning void.

The correct type is deduced, as in Wrap(someFunc, 5, true), even for functions returning void.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • Good to know, thanks. Eliminates the need for a type in <> (which I would've just cheaped out on and said `#define vwrap wrap` or something). I'll see if it works out as well. For whatever reason `std::forward` and `Args && args...` don't like working, but the winapi is in C, so apparently (previous question) forward provides no benefit there anyways. – chris Mar 08 '12 at 23:42
  • Never mind, forward decided to work again. It might be a discrepancy between the compiler I use at home and the one I use on my flash drive at school. – chris Mar 08 '12 at 23:46
  • Yes, this works perfectly and keeps the same syntax. One change I had to make was to change `Args&& args...` to `Args&&... args...`. Thanks. – chris Mar 08 '12 at 23:57
2

To get around this, I tried creating a version of the template where I replaced TRet with void. Unfortunately, this actually just causes an ambiguity of which one to use.

That should work, I believe, because void is more specialised than TRet, but as you point out it doesn't. I may be missing something, but at any rate, it doesn't matter, you can prevent the TRet overload from being selected.

template<typename TFun, typename... TArgs>
auto Wrap(TFun api, TArgs&&... args) ->
typename std::enable_if<
    !std::is_void<typename std::result_of<TFun(TArgs...)>::type>::value,
    typename std::result_of<TFun(TArgs...)>::type
>::type
{
    auto result = api(std::forward<TArgs&&>(args)...);
    return result;
}

template<typename TFun, typename... TArgs>
auto Wrap(TFun api, TArgs&&... args) ->
typename std::enable_if<
    std::is_void<typename std::result_of<TFun(TArgs...)>::type>::value,
    typename std::result_of<TFun(TArgs...)>::type
>::type
{
    api(std::forward<TArgs&&>(args)...);
}

void WINAPI f1()
{
}

void WINAPI f2(double)
{
}

int WINAPI f3()
{
    return 0;
}

int WINAPI f4(double)
{
    return 0;
}

int main() {
    Wrap(f1);
    Wrap(f2, 0);
    return Wrap(f3) * Wrap(f4, 0);
}

Update: adjusted to allow for conversions from argument type to parameter type.

  • This code works fine. However, when trying to match my other code, I discovered that the ambiguity comes when the function has any number of parameters. When it has no parameters, the ambiguity is removed. – chris Mar 08 '12 at 23:51
  • @chris Ah, right, then it starts failing for me too. However, it's easily fixed by removing the overload that shouldn't be used, will edit. –  Mar 08 '12 at 23:53
  • That's some fancy work there :p I haven't really done much with templates other than some basic math functions, so I'm a bit useless with all the syntax regarding the newer ones. This works fine for me too, though, and now I don't know whose answer to use :/ – chris Mar 09 '12 at 00:06
  • @chris I wouldn't say this is better or worse than Alexandre C.'s answer, so if you already decided to use that, by all means continue. If you did not yet commit to that, this *may* be closer to what you have already. I don't think there's much of a difference otherwise. –  Mar 09 '12 at 00:15
  • I've been doing something else so far, but either one works the same, so it comes down to a mix of style and space taken up in the code. At least if the one I use doesn't work out, I can try the other :) – chris Mar 09 '12 at 00:17
  • Just wondering, if the API function is, say, `void a (double d);` and you call `Wrap (a, 5);`, it won't work because (I assume) 5 is implicitly casted to an `int`, which doesn't match the `double` parameter. Instead the call must be `Wrap (a, (double)5);` Is there any easy way to remove this restriction? This little annoyance existed before this question. I must say it does make the code very articulated though :p – chris Mar 09 '12 at 01:56
  • @chris That's because the same `TArgs` are used for the wrapped function's parameters and for the wrapper function's parameters. Split the two into `typename... TArgsWrapped` and `typename... TArgsWrapper` and it will work. (It'll take some time before I can update my answer with this.) –  Mar 09 '12 at 06:40
  • Ok, I'll experiment with that. I'm not fully understanding of how this all works (I'll go through some lessons on templates again), but it's a great start. – chris Mar 09 '12 at 13:35
  • Ah, I got it working :) I'll update your answer if you're busy then. I hope it's what you were expecting. – chris Mar 09 '12 at 13:48
  • @chris That would've been fine, but I meant I would update my answer later, and I have now, in a somewhat altered form :) –  Mar 09 '12 at 18:01
  • Pretty spiffy, as if it wasn't before. – chris Mar 09 '12 at 18:45
1

Promoting from comment to answer I understanding why specializing the return type as void didn't work (can't disambiguate on return types) but specializing with void and adding an additional parameter should work, what happened? You may have to invoke with explicit types.

Tod
  • 8,192
  • 5
  • 52
  • 93
  • I apologize for changing the accepted answer, as this did work fine, but Alexandre C.'s answer keeps the *exact* same syntax as I was using before without the use of any #define statements or things like that. The trade off is just a bit of extra code; nothing in comparison to the entire wrapper. – chris Mar 09 '12 at 00:00
  • No problem, I think the accepted answer should be the one you want someone in a similar situation to see when they go searching. Plus the fact that Alexandre's answer deduces automatically is a bug plus. Just getting the upvote is nice. – Tod Mar 09 '12 at 19:03