Michael Kiros
Michael Kiros

Reputation: 583

Callable Objects with different calling conventions

At the moment I'm building functors (callable types) for the different calling conventions (__stdcall, __cdecl, __fastcall etc.). With the wrappers I'll be able to do something like this:

void __stdcall foo(int arg)
{
    std::printf("arg: %i\n", arg);
}

int main(int, char**)
{
    Function<void, int> v{foo};
    v(1337);

    return EXIT_SUCCESS;
}

At the moment I have built a wrapper for the __stdcall calling convention that is able to call any __stdcall function as long as the correct parameters are specified and the correct arguments are passed in. The class looks like this:

template <typename ReturnT, typename... Args>
class Function
{
    // NOTE: This version of my callable types
    // only supports the __stdcall calling
    // convention. I need support for __cdecl,
    // __fastcall and also __thiscall.
    using return_t = ReturnT;
    using callable_t = return_t(__stdcall*)(Args...);

private:
    callable_t mCallable;

public:
    template <typename FuncT>
    Function(FuncT const &func) :
        mCallable(func)
    {
        ;
    }

    void operator()(Args&&... args)
    {
        mCallable(std::forward<Args>(args)...);
    }
};

With that in hand I decided to build the other wrappers but I figured that typing out the same piece of code and changing the calling convention inside of the using declaration for callable_t is more work than needed. So I wanted to find a way to build about 4 variants of callable types (for each calling convention) but couldn't find a way to do it.

So far I have tried to use an enum as a non-type template parameter like this:

template <CallingConvention Call, typename ReturnT, typename... ArgsT>
class Function
{
    // ...
};

But I don't know how to iterate the Call object's type and establish the required type (I tried utilizing std::is_same/std::enable_if but that was a dead end). I also tried template specialization with code like this:

struct StdcallT { ; };
struct CdeclT { ; };
struct FastcallT { ; };

template <typename CallT>
struct BaseT { };

template <> struct BaseT<StdcallT> { using CallableT = void(__stdcall*)(); };
template <> struct BaseT<CdeclT> { using CallableT = void(__cdecl*)(); };
template <> struct BaseT<FastcallT> { using CallableT = void(__fastcall*)(); };

template <typename CallT>
class Function
{
    using CallableT = typename BaseT<CallT>::CallableT;
};

But I wasn't thinking of the rest of the arguments (return type + parameters) so this can't work too.

So does anyway have any ideas what I can do? One method I'm thinking of is doing a switch on the non-type parameter and calling the correct one like this:

template <CallingConvention Call, typename ReturnT, typename... ArgsT>
class Function
{
    void operator()(ArgsT&&... args)
    {
        switch(Call)
        {
            case CallingConvention::Cdecl:
                // Call a __cdecl version
                break;
            case CallingConvention::Stdcall:
                // Call an __stdcall version
                break;
            // And so on...
        }
    }
};

And despite this looking like a working solution I was wondering if there was some good alternatives that I'm not thinking of.

Any ideas?

Upvotes: 8

Views: 1398

Answers (2)

Well once you define tags for each calling convention, you can use tag dispatch regularly:

#include <iostream>
#include <type_traits>

struct cdecl_tag    { typedef void ( __attribute__((cdecl))    *type)(); };
struct stdcall_tag  { typedef void ( __attribute__((stdcall))  *type)(); };
struct fastcall_tag { typedef void ( __attribute__((fastcall)) *type)(); };

constexpr void get_func_calling_convention_tag () {};

template<typename R, typename... Args>
constexpr cdecl_tag
get_func_calling_convention_tag (R (__attribute__((cdecl)) *)(Args...))
{ return {}; }

template<typename R, typename... Args>
constexpr stdcall_tag
get_func_calling_convention_tag (R (__attribute__((stdcall)) *)(Args...))
{ return {}; }

template<typename R, typename... Args>
constexpr fastcall_tag
get_func_calling_convention_tag (R (__attribute__((fastcall)) *)(Args...))
{ return {}; }

#define CALLING_CONVENTION_TAG(func) \
decltype(get_func_calling_convention_tag(&func))

int  __attribute__((cdecl))   foo (char) { return 0; }
long __attribute__((stdcall)) bar (int)  { return 0; }

int main()
{
    std::cout << std::is_same<CALLING_CONVENTION_TAG(foo),
                              cdecl_tag>::value                   << '\n'
              << std::is_same<CALLING_CONVENTION_TAG(bar),
                              stdcall_tag>::value                 << '\n'
              << std::is_same<CALLING_CONVENTION_TAG(foo), 
                              CALLING_CONVENTION_TAG(bar)>::value << std::endl;

    return 0;
}

See it in action : http://ideone.com/HSZztX
This can of course be developed further; the tags may have a rebind variadic member template that returns a function pointer type with the appropriate calling convention specified.

I suppose you may even reduce the copying and pasting by having tag defintions neatly in a macro.

Upvotes: 1

tkellehe
tkellehe

Reputation: 679

If you still want to use the enumerated template argument, you can use specialization to accomplish this.

enum CallingConvention { __stdcall, ... };

template < CallingConvention Call >
struct implement {
    template</* Template arguments for call method */>
    static ReturnT call(/* arguments to run method */);
};

template < CallingConvention Call, typename ReturnT, typename... ArgsT >
class Function
{
    // ...
    template <typename FuncT>
    Function(FuncT const &func) : mCallable(func), mCall(Call) {}
    CallingConvention const mCall;

    return_t operator()(ArgsT&&... args) {
        return implement<Call>::call</* Template arguments for call method */>(/* arguments to run method */);
    };
};

template < >
struct implement< __stdcall > {
    template</* Template arguments for call method */>
    static ReturnT call(/* arguments to run method */) {
        // Special implementation...
    }
};

That will be better than a switch statement.

(Sorry about the comments for the template arguments I am not quite familiar with how that works)

Here is where I got the idea for what I did.


Hope this helps!

Upvotes: 1

Related Questions