2015-12-29 9 views
8

W tej chwili buduję funktory (typy wywoływalne) dla różnych konwencji wywoływania (__stdcall, __cdecl, __fastcall itd.). Z owijarki będę w stanie zrobić coś takiego:Podlegające obiekty z różnymi konwencjami wywoływania

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; 
} 

W tej chwili zbudowali otoczka do __stdcall wywołującego konwencji, która jest w stanie wywołać dowolną funkcję __stdcall ile poprawne parametry są określone i poprawne argumenty są przekazywane w klasie wygląda następująco:.

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)...); 
    } 
}; 

Mając to na rękę postanowiłem zbudować inne owijarki ale pomyślałem, że pisanie na ten sam kawałek kodu i zmieniając konwencję telefonicznej wewnątrz deklaracja użycia dla callable_t to więcej pracy niż potrzeba. Tak więc chciałem znaleźć sposób na zbudowanie około 4 wariantów typów wywoływalnych (dla każdej konwencji wywoływania), ale nie mogłem znaleźć sposobu na zrobienie tego.

Do tej pory starałem się używać enum jako parametr szablonu non typu jak ten:

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

Ale nie wiem, jak iteracyjne typ obiektu telefonu rozmowy i ustalenia wymaganego typu (I próbowałem używać std :: is_same/std :: enable_if ale to był ślepy zaułek). Próbowałem też specjalizację szablonu z kodem tak:

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; 
}; 

Ale nie myślał o resztę argumentów (typ zwrotny + parametry), więc to nie może pracować zbyt.

Tak czy inaczej masz jakieś pomysły, co mogę zrobić? Jedną z metod, które mam na myśli robi przełącznik na parametr typu non-i nazywając poprawny takiego:

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... 
     } 
    } 
}; 

a mimo to wygląda jak roztworu roboczego Zastanawiałem się, czy nie było kilka dobrych alternatyw, które Nie myślę o tym.

Wszelkie pomysły?

+0

Po co odnawiać koło? 'std :: function' powinno już być w stanie przechowywać dowolny wskaźnik funkcyjny lub funktor, niezależnie od konwencji wywoływania, o ile można go wywoływać z podanymi argumentami. Czy to nie działa dla Ciebie, czy ma jakieś konkretne wady, które sprawiły, że zaczynasz od niestandardowej implementacji? – hvd

+0

@hvd załóżmy, że masz std :: unordered_map łańcuchy mapujące takie jak "glCreateProgram" i "glCreateShader" załadowane z opengl32.dll na ich adresy symboli. Chcesz utworzyć szablonową funkcję function_cast , które użyjesz void dla żadnych args/zwraca i std :: tuple dla args/no args, ponieważ są one kompatybilne binarnie. w obecnej postaci, std: funkcja nie działa dla stdcall, więc jeśli jest to wymagane, wymagana jest kontrola w przypadku jakiejś fałszywej konwencji wywołującej, w oparciu o którą przełączasz wywoływanie konwencji konwencji. – Dmitry

+0

@Dmitry "std: funkcja nie działa dla stdcall" - Starsze wersje GCC (do GCC 5) zignorowały konwencję wywołującą w manglingu nazw, powodując błędy linkera, ale zostało to naprawione w połowie 2015 r., I nie powinno być problemami z 'std :: function' dla niekonwencjonalnych konwencji wywoływania od tego czasu przynajmniej w tym kompilatorze. Inne kompilatory * powinny * działać w ten sam sposób. Jeśli nie, czy możesz podać szczegółowe informacje na temat tego, który jest? – hvd

Odpowiedz

1

Jeśli nadal chcesz użyć argumentu z wyliczonym szablonem, możesz użyć specjalizacji, aby to osiągnąć.

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... 
    } 
}; 

To będzie lepsze niż instrukcja switch.

(Niestety o komentarzach do argumentów szablonu nie jestem całkiem pewny, jak to działa)

Tu mam pojęcia o co mi did.


Mam nadzieję, że to pomoże!

1

Dobrze raz zdefiniować znaczniki dla każdego wywołującego konwencji, można regularnie używać znaczników wysyłkowy:

#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; 
} 

Zobacz go w akcji: http://ideone.com/HSZztX
Może to oczywiście być dalej rozwijane; znaczniki mogą mieć szablon stylisty rebind, który zwraca typ wskaźnika funkcji z odpowiednią konwencją wywołania.

Przypuszczam, że możesz nawet zredukować kopiowanie i wklejenie, dzięki zdefiniowaniu tagów w makrze.

Powiązane problemy