2016-10-05 13 views
7

Tôi đang cố gắng để gõ xóa một đối tượng và chạy vào một chút của một vấn đề mà tôi hy vọng một ai đó ở đây có thể được có chuyên môn trong.C++ Type-tẩy xoá của một hàm mẫu sử dụng lambdas

tôi thiên đường' t đã có một vấn đề loại-erasing tùy ý không-templated chức năng; cho đến nay những gì tôi đã làm là tạo một bộ sưu tập các con trỏ hàm tùy chỉnh static "ảo". Đây là tất cả được quản lý với lambdas không chụp, vì họ phân hủy thành con trỏ tự do chức năng:

template<typename Value, typename Key> 
class VTable { 
    Value (*)(const void*, const Key&) at_function_ptr = nullptr; 
    // ... 

    template<typename T> 
    static void build_vtable(VTable* table) { 
     // normalizes function into a simple 'Value (*)(const void*, const Key&)'' type 
     static const auto at_function = [](const void* p, const Key& key) { 
      return static_cast<const T*>(p)->at(key); 
     } 
     // ... 
     table->at_function_ptr = +at_function; 
    } 
    // ... 
} 

(Có nhiều chức năng helper/aliases được bỏ qua cho ngắn gọn)

Đáng buồn cùng này cách tiếp cận không hoạt động với hàm template.

Mong muốn của tôi là dành cho lớp kiểu xóa để có một cái gì đó tương tự như sau:

template<typename U> 
U convert(const void* ptr) 
{ 
    return cast<U>(static_cast<const T*>(ptr)); 
} 

nơi:

  • cast là một chức năng miễn phí,
  • U là loại hạnh phúc được đúc thành,
  • T là loại bị xóa loại cơ bản đang được đúc từ và
  • ptr là con trỏ loại đã xóa theo cùng một thành ngữ ở trên để xóa loại.

[Chỉnh sửa: Vấn đề ở trên là T không được biết đến từ chức năng convert; hàm duy nhất biết về loại của T trong ví dụ là build_vtable. Điều này có thể yêu cầu thay đổi thiết kế]

Lý do điều này trở nên khó khăn là dường như không có cách nào đơn giản để xóa cả hai loại một cách độc lập. Kỹ thuật tẩy xóa kiểu cổ điển/thành ngữ của một lớp cơ sở không hoạt động ở đây, vì bạn không thể có chức năng a virtualtemplate. Tôi đã thử nghiệm với một mô hình giống như khách truy cập với ít thành công cho các lý do tương tự như ở trên.

Có ai có kinh nghiệm về loại tẩy xóa có bất kỳ đề xuất hoặc kỹ thuật nào có thể được sử dụng để đạt được những gì Tôi đang cố gắng làm gì? Tốt hơn là trong mã C++ 14 phù hợp tiêu chuẩn. Hoặc, có lẽ là có thay đổi thiết kế có thể tạo điều kiện cho cùng một khái niệm mong muốn ở đây?

Tôi đã tìm kiếm câu trả lời này trong một thời gian ngắn và không có nhiều may mắn. Có một vài trường hợp tương tự như những gì tôi đang cố gắng làm, nhưng thường có đủ sự khác biệt mà các giải pháp dường như không áp dụng cho cùng một vấn đề (Vui lòng cho tôi biết nếu tôi sai).

Dường như hầu hết các bài đọc/blog về các chủ đề này đều có xu hướng đề cập đến kỹ thuật tẩy xóa cơ bản, nhưng không phải những gì tôi đang tìm kiếm ở đây!

Cảm ơn!

Lưu ý: vui lòng không khuyên bạn nên Boost. Tôi đang ở trong một môi trường mà tôi không thể sử dụng thư viện của họ, và không muốn giới thiệu sự phụ thuộc đó vào cơ sở mã.

+0

Không thể 'cast' là hàm functor và sau đó gọi nó là 'cast (tag {}, truyền (tag {}, ptr))'? – Jarod42

+0

@ Jarod42 Vấn đề là 'T' không được biết đến từ bên trong' chuyển đổi', vì vậy nó không đơn giản như việc tạo ra một 'tag ' tại thời điểm đó. Hàm duy nhất trong ví dụ biết loại 'T' là' build_vtable' – Bitwize

+0

Đầu tiên, nó không phải là một hàm 'mẫu'; nó là một hàm 'mẫu'.Từ ngữ là điều quan trọng cần hiểu ở đây. Vì vậy, nó không phải là một chức năng thực tế. Hàm thực tế là 'chuyển đổi ' như Yakk mô tả. Vì vậy, bạn sẽ cần 'convert_U_function_ptr' bên trong' VTable' của bạn cho mỗi 'U' loại – zahir

Trả lời

5

Mỗi khác biệt convert<U> là một kiểu xóa riêng biệt.

Bạn có thể nhập xóa danh sách các chức năng như vậy, lưu trữ phương pháp thực hiện nó trong từng trường hợp. Giả sử bạn có Us..., hãy xóa tất cả convert<Us>....

Nếu Us... ngắn gọn thì điều này thật dễ dàng.

Nếu lâu thì đây là một cơn đau. Có thể là phần lớn trong số này có thể là rỗng (như trong hoạt động là bất hợp pháp), vì vậy bạn có thể thực hiện vtable thưa thớt để tính đến điều này, vì vậy vtable của bạn không lớn và đầy 0. Điều này có thể được thực hiện bằng cách xóa một hàm (sử dụng kỹ thuật vtable chuẩn) trả về một tham chiếu (hoặc một accessor loại đã xóa) để nói vtable thưa thớt ánh xạ từ std::typeindex tới bộ chuyển đổi U-placement-constructor (ghi vào void* trong chữ ký). Sau đó bạn chạy hàm đó, trích xuất mục nhập, tạo một bộ đệm để lưu trữ U trong, gọi bộ chuyển đổi U-vị trí-constructor đi qua trong bộ đệm đó.

Tất cả điều này xảy ra trong chức năng type_erased_convert<U> của bạn (chính nó không bị xóa) để người dùng cuối không phải quan tâm đến các chi tiết nội bộ.

Bạn biết đấy, đơn giản.

Hạn chế là danh sách các loại chuyển đổi có thể là U được hỗ trợ cần phải được đặt trước vị trí loại xóa. Cá nhân, tôi sẽ giới hạn type_erased_convert<U> để chỉ được gọi trên cùng một danh sách các loại U và chấp nhận rằng danh sách này phải cơ bản ngắn.


Hoặc bạn có thể tạo một số biểu đồ chuyển đổi khác cho phép bạn cắm loại và xác định cách tiếp cận một loại khác có thể thông qua một số trung gian chung. Hoặc bạn có thể sử dụng một ngôn ngữ kịch bản hoặc bytecode bao gồm một trình biên dịch đầy đủ trong giai đoạn thực hiện, cho phép các phương pháp loại-erased được biên dịch chống lại một loại hoàn toàn độc lập mới khi được gọi. Quay lại đầu trang |


std::function< void(void const*, void*) > constructor; 

std::function< constructor(std::typeindex) > ctor_map; 

template<class...Us> 
struct type_list {}; 

using target_types = type_list<int, double, std::string>; 

template<class T, class U> 
constructor do_convert(std::false_type) { return {}; } 
template<class T, class U> 
constructor do_convert(std::true_type) { 
    return [](void const* tin, void* uout) { 
    new(uout) U(cast<U>(static_cast<const T*>(ptr))); 
    }; 
} 

template<class T, class...Us> 
ctor_map get_ctor_map(std::type_list<Us...>) { 
    std::unordered_map< std::typeindex, constructor > retval; 
    using discard = int[]; 
    (void)discard{0,(void(
    can_convert<U(T)>{}? 
     (retval[typeid(U)] = do_convert<T,U>(can_convert<U(T)>{})),0 
    : 0 
),0)...}; 
    return [retval](std::typeindex index) { 
    auto it = retval.find(index); 
    if (it == retval.end()) return {}; 
    return it->second; 
    }; 
} 

template<class T> 
ctor_map get_ctor_map() { 
    return get_ctor_map<T>(target_types); 
} 

Bạn có thể thay thế các unordered_map với một chồng dựa trên một khi nó là nhỏ gọn. Lưu ý rằng std::function trong MSVC bị giới hạn khoảng 64 byte hoặc hơn?


Nếu bạn không muốn danh sách cố định các loại nguồn/đích, chúng tôi có thể phân tách điều này.

  • Phơi bày typeindex của các loại lưu trữ trong container loại tẩy xoá, và khả năng để có được tại void const* trỏ vào nó.

  • Tạo đặc điểm kiểu bản đồ loại T vào danh sách các loại Us... hỗ trợ chuyển đổi. Sử dụng kỹ thuật trên để lưu trữ các hàm chuyển đổi này trong bản đồ (toàn cục). (Lưu ý rằng bản đồ này có thể được đặt trong bộ nhớ tĩnh, vì bạn có thể suy ra kích thước của bộ đệm cần thiết vv Nhưng sử dụng static unordered_map thì dễ dàng hơn).

  • Tạo đặc điểm loại thứ hai ánh xạ loại U vào danh sách các loại Ts... hỗ trợ chuyển đổi từ.

  • Trong cả hai trường hợp, hàm convert_construct(T const* src, tag_t<U>, void* dest) được gọi để thực hiện chuyển đổi thực tế.

Bạn sẽ bắt đầu với một tập hợp các mục tiêu phổ quát type_list<int, std::string, whatever>. Một loại cụ thể sẽ làm tăng thêm nó bằng cách có một danh sách mới.

Đối với loại T xây dựng bảng chuyển đổi thưa thớt của chúng tôi, chúng tôi sẽ thử từng loại mục tiêu. Nếu không tìm thấy tình trạng quá tải của convert_construct, bản đồ sẽ không được điền cho trường hợp đó. (Tạo các lỗi biên dịch thời gian cho các loại được thêm một cách rõ ràng để làm việc với T là một tùy chọn).

Ở đầu bên kia, khi chúng ta gọi là type_erased_convert_to<U>(from), chúng ta tìm kiếm một khác nhau bảng mà các bản đồ kiểu U chéo typeindex để chuyển đổi U(*)(void const* src). Cả hai bản đồ từ T được lấy từ số T bị xóa loại và mã nhận được trong mã gói được tư vấn để tìm trình chuyển đổi.

Hiện tại, điều này không cho phép một số loại chuyển đổi nhất định. Ví dụ: một loại T chuyển đổi từ bất kỳ thứ gì bằng phương thức .data() -> U*.size() -> size_t cần liệt kê rõ ràng mọi loại chuyển đổi từ đó.

Bước tiếp theo sẽ là nhận chuyển đổi nhiều bước. Chuyển đổi nhiều bước là nơi bạn dạy cho T của mình để chuyển đổi thành một số loại nổi tiếng (set of) và chúng tôi dạy cho U để chuyển đổi từ một loại nổi tiếng tương tự (tập hợp). (Sự nổi tiếng của các loại này là tùy chọn, tôi sẽ thừa nhận, tất cả những gì bạn cần biết là cách tạo và tiêu diệt chúng, dung lượng cần thiết và cách kết hợp các tùy chọn T -to và U -from để sử dụng chúng như là một trung gian.)

Điều này có vẻ như đã được thiết kế. Nhưng khả năng chuyển đổi thành std::int64_t và chuyển đổi từ đó sang bất kỳ loại tích phân đã ký nào là ví dụ về điều này (và tương tự cho uint64_t và chưa ký).

Hoặc khả năng chuyển đổi sang từ điển cặp khóa-giá trị, sau đó kiểm tra từ điển này ở phía bên kia để xác định xem chúng tôi có thể chuyển đổi từ đó hay không.

Khi bạn đi xuống con đường này, bạn sẽ muốn kiểm tra các hệ thống đánh máy lỏng lẻo bằng nhiều ngôn ngữ kịch bản và bytecode khác nhau để chọn cách chúng thực hiện.

+0

Mặc dù đây là một gợi ý tuyệt vời, tôi không chắc liệu điều này có áp dụng cho vấn đề hiện tại hay không. Tôi đã thực hiện một chỉnh sửa nhỏ để nhấn mạnh vấn đề tôi đã gặp phải; đó là hàm 'convert' không có bất kỳ kiến ​​thức nào về' T'. Hàm duy nhất trong ví dụ trên thực hiện là 'build_vtable', nếu không kiểu chỉ đơn giản là' void * '. – Bitwize

+0

Mặc dù tôi cảm thấy điều này là ít có khả năng, tôi đã ban đầu hy vọng để thực hiện điều này mà không yêu cầu bất kỳ phân bổ đống (chẳng hạn như sử dụng một bản đồ). Mặc dù tôi cho rằng vì đây là tĩnh, nó có thể được thực hiện với một danh sách xâm nhập của các cặp ('std :: type_index', function ptr). – Bitwize

+0

@Bitwize Có, 'type_erased_convert' không có kiến ​​thức về T trong đề xuất ở trên của tôi. Hoạt động loại đã xóa là '(void *) ->' map từ 'typeindex' thành' std :: function 'Bản đồ thưa thớt không cần phải phân bổ đống. – Yakk

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