Check at Compile-Time if Template Argument is void
Asked Answered
E

3

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.

Ebullient answered 8/3, 2012 at 21:48 Comment(6)
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.Sheilahshekel
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().Springing
I didn't think of combining the two. I'll try it out. Thanks for the heads-up on the typeid issue too.Ebullient
Your explicit types point proved to do the trick. All I have to do when calling a function returning void is Wrap <DummyType> (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 :)Ebullient
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.Coraleecoralie
@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.Ebullient
S
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.

Stylopodium answered 8/3, 2012 at 22:50 Comment(3)
Good to know, thanks. Eliminates the need for a type in <> (which I would've just cheaped out on and said #define vwrap wrap<int> 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.Ebullient
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.Ebullient
Yes, this works perfectly and keeps the same syntax. One change I had to make was to change Args&& args... to Args&&... args.... Thanks.Ebullient
R
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.

Rollo answered 8/3, 2012 at 23:43 Comment(11)
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.Ebullient
@Ebullient 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.Rollo
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 :/Ebullient
@Ebullient 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.Rollo
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 :)Ebullient
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 :pEbullient
@Ebullient 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.)Rollo
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.Ebullient
Ah, I got it working :) I'll update your answer if you're busy then. I hope it's what you were expecting.Ebullient
@Ebullient That would've been fine, but I meant I would update my answer later, and I have now, in a somewhat altered form :)Rollo
Pretty spiffy, as if it wasn't before.Ebullient
S
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.

Sheilahshekel answered 8/3, 2012 at 22:31 Comment(2)
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.Ebullient
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.Sheilahshekel

© 2022 - 2024 — McMap. All rights reserved.