简体   繁体   中英

Check at Compile-Time if Template Argument is void

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.

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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM