2015-12-29 14 views
8

Hiện tại tôi đang xây dựng các functors (các loại có thể gọi) cho các quy ước gọi khác nhau (__stdcall, __cdecl, __fastcall, v.v.). Với những gói tôi sẽ có thể làm điều gì đó như thế này:Các đối tượng có thể gọi với các quy ước gọi khác nhau

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

Tại thời điểm này tôi đã xây dựng một wrapper cho __stdcall gọi ước rằng có thể gọi bất kỳ chức năng __stdcall miễn là các thông số chính xác được quy định và những lập luận chính xác được thông qua vào lớp trông như thế này:.

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

với điều đó trong tay tôi quyết định xây dựng những gói khác nhưng tôi figured mà gõ ra cùng một mảnh mã và thay đổi quy ước gọi bên trong khai báo sử dụng cho callable_t là công việc nhiều hơn cần thiết. Vì vậy, tôi muốn tìm một cách để xây dựng khoảng 4 biến thể của các loại có thể gọi (cho mỗi quy ước gọi) nhưng không thể tìm thấy một cách để làm điều đó.

Cho đến nay tôi đã cố gắng sử dụng một enum như một tổ chức phi kiểu mẫu tham số như thế này:

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

Nhưng tôi không biết làm thế nào để lặp loại đối tượng gọi và thiết lập các loại yêu cầu (I đã thử sử dụng std :: is_same/std :: enable_if nhưng đó là một kết thúc chết). Tôi cũng đã cố gắng mẫu chuyên môn với mã như thế này:

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

Nhưng tôi đã không nghĩ đến việc phần còn lại của các đối số (kiểu trả về + thông số) vì vậy đây không thể làm việc quá.

Vì vậy, dù sao có bất kỳ ý tưởng nào tôi có thể làm? Một phương pháp Tôi đang nghĩ đến việc đang làm một công tắc trên thông số không loại và gọi đúng như thế này:

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

Và mặc dù này trông như một giải pháp làm việc tôi đã tự hỏi nếu có là một số lựa chọn thay thế tốt mà Tôi không nghĩ đến.

Bất kỳ ý tưởng nào?

+0

Tại sao tái tạo bánh xe? 'std :: function' đã có thể lưu trữ bất kỳ con trỏ hàm hoặc hàm functor nào, bất kể quy ước gọi, miễn là nó có thể gọi với các đối số mà bạn chỉ định. Nó không hoạt động cho bạn hay nó có một số nhược điểm cụ thể khiến bạn bắt đầu triển khai tùy chỉnh của mình? – hvd

+0

@hvd giả sử bạn có một chuỗi std :: unordered_map ánh xạ như "glCreateProgram" và "glCreateShader" được tải từ opengl32.dll đến địa chỉ biểu tượng của chúng. Bạn muốn tạo một hàm templated function_cast mà bạn sẽ sử dụng void cho no args/returns, và std :: tuple cho args/no args, vì chúng tương thích nhị phân. như nó đứng, std: chức năng không hoạt động cho stdcall, do đó, một thời gian chạy nếu kiểm tra là cần thiết trên một số quy ước gọi điện thoại giả dựa trên đó bạn chuyển đổi gọi quy ước đúc. – Dmitry

+0

@Dmitry "std: chức năng không hoạt động cho stdcall" - Phiên bản cũ của GCC (lên đến GCC 5) bỏ qua quy ước gọi trong tên mangling, gây ra lỗi liên kết, nhưng điều này đã được cố định giữa năm 2015, và không nên là vấn đề với 'std :: function' đối với các quy ước gọi không mặc định kể từ đó ít nhất là trong trình biên dịch đó. Các trình biên dịch khác * nên * hoạt động theo cùng một cách. Nếu không, bạn có thể cung cấp chi tiết về cái nào không? – hvd

Trả lời

1

Nếu bạn vẫn muốn sử dụng đối số mẫu được liệt kê, bạn có thể sử dụng chuyên môn để thực hiện việc này.

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

Điều đó sẽ tốt hơn câu lệnh chuyển đổi.

(Xin lỗi về những ý kiến ​​cho các đối số mẫu Tôi không khá quen thuộc với cách làm việc)

Đây là nơi tôi đã có ý tưởng cho những gì tôi did.


Hy vọng điều này sẽ hữu ích!

1

Vâng khi bạn định nghĩa các thẻ cho mỗi quy ước gọi, bạn có thể sử dụng thẻ công văn thường xuyên:

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

Nhìn thấy nó trong hành động: http://ideone.com/HSZztX
này tất nhiên có thể được tiếp tục phát triển; các thẻ có thể có mẫu rebind variadic thành viên trả về một kiểu con trỏ hàm với quy ước gọi thích hợp được chỉ định.

Tôi cho rằng bạn thậm chí có thể giảm quá trình sao chép và dán bằng cách có defintions thẻ gọn gàng trong macro.

Các vấn đề liên quan