2016-10-05 19 views
7

Próbuję wpisać usunąć obiekt i wpadł trochę problem, który mam nadzieję, że ktoś tutaj może mieć wiedzę w.C++ typu usuwanie szablonu z wykorzystaniem funkcji lambdy

I haven” t miało problem z wymazywaniem dowolnych niesformatowanych funkcji; do tej pory to, co robiłem, tworzy niestandardową kolekcję wskaźników funkcyjnych "wirtualny stół" static . To wszystko udało się bez przechwytywania lambdas, ponieważ rozpadają się na wskaźnikach wolnego funkcyjnych:

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

(Istnieje więcej funkcji pomocnika/aliasy, które zostały pominięte dla zwięzłości)

Niestety to samo podejście nie działa z funkcją template.

Moim pragnieniem jest, by klasa typu skasowane mieć coś podobnego do następującego:

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

gdzie:

  • cast to darmowa funkcja,
  • U jest typ istota odlewane do,
  • T jest typem usuniętego typu, z którego są rzucane, i
  • ptr jest wykasowanym typem wskaźnikiem, który podąża za tym samym idiomem powyżej dla typu wymazania.

[Edycja: Emisja powyżej jest to, że nie jest znany T z funkcji convert; jedyną funkcją, która wie o typie T w tym przykładzie jest build_vtable. Może to po prostu wymaga zmiany projektowe]

Powodem stało się wyzwaniem jest to, że nie wydaje się, aby w prosty sposób usunąć wpisać oba typy samodzielnie. Klasyczna/idiomatyczna technika usuwania typu klasy bazowej nie działa tutaj, ponieważ nie można uzyskać funkcji o wartości . Eksperymentowałem z modelem podobnym do odwiedzającego, z niewielkim powodzeniem dla podobnych przyczyn z powyższego.

Czy ktoś z doświadczeniem w usuwaniu typu ma jakieś sugestie lub techniki, które można zastosować, aby osiągnąć to, co próbuję zrobić? Korzystnie w zgodnym ze standardami kodzie C++ 14. A może jest tam zmiana projektu, która może ułatwić tę samą koncepcję, o którą tutaj chodzi?

Szukałem tej odpowiedzi od jakiegoś czasu i nie miałem szczęścia. Jest kilka przypadków, które są podobne do tego, co próbuję zrobić, ale często z wystarczającą różnicą, że rozwiązania nie wydają się dotyczyć tego samego problemu (proszę dać mi znać, jeśli się mylę!).

Wygląda na to, że większość odczyty/blogi na te tematy zwykle obejmują podstawowe techniki usuwania tekstu, ale nie to, czego szukam tutaj!

Dzięki!

Uwaga: nie polecam Zwiększenia. Znajduję się w środowisku, w którym nie mogę korzystać z ich bibliotek, i nie chcę, aby ta zależność była zależna od kodu.

+0

Nie można "obsadzić" jako funktora, a następnie nazwać go "rzutowaniem" (znacznik {}, rzutowanie (znacznik {}, ptr)) '? – Jarod42

+0

@ Jarod42 Problem polega na tym, że 'T' nie jest znane z' convert ', więc nie jest tak proste jak stworzenie 'tagu ' w tym miejscu. Jedyną funkcją w przykładzie, który zna typ 'T' jest' build_vtable' – Bitwize

+0

Po pierwsze, nie jest to funkcja 'template'; jest to funkcja 'szablon'.Konieczne jest tutaj sformułowanie słów. Tak więc nie jest to faktyczna funkcja. Faktyczna funkcja to 'convert ' jak opisuje Yakk. Dlatego będziesz potrzebować 'convert_U_function_ptr' w swoim' VTable' dla każdego 'U' type – zahir

Odpowiedz

5

Każdy odrębny convert<U> jest wyraźnym wymazaniem typu.

Możesz wpisać listę takich funkcji, przechowując metodę wykonania w każdym przypadku. Załóżmy, że masz Us..., wpisz wymazać wszystkie z convert<Us>....

Jeśli Us... jest krótki, jest to łatwe.

Jeśli jest długi, to jest ból.

Jest możliwe, że większość z nich może mieć wartość null (ponieważ w działaniu jest nielegalna), więc możesz zaimplementować rzadki vtable, który bierze to pod uwagę, więc twój vtable nie jest duży i pełen zer. Można to zrobić, usuwając typ funkcji (przy użyciu standardowej techniki vtable), która zwraca odniesienie (lub wymazany typ) do wspomnianego rzadkiego vtable, który odwzorowuje od std::typeindex na konwerter konstruktora U-layoutu (który zapisuje do void* w podpis). Następnie uruchom tę funkcję, wyodrębnij wpis, utwórz bufor do przechowywania U, wywołaj konwerter konstruktora U-U, przechodząc do tego bufora.

Wszystko to dzieje się w funkcji type_erased_convert<U> (która sama w sobie nie jest kasowana), dzięki czemu użytkownicy końcowi nie muszą dbać o szczegóły wewnętrzne.

Wiesz, proste.

Ograniczeniem jest to, że lista możliwych typów przekształcania U, które są obsługiwane, musi znajdować się przed lokalizacją usunięcia typu. Osobiście ograniczyłbym type_erased_convert<U> do wywoływania tylko na tej samej liście typów U i akceptuję, że ta lista musi być zasadniczo krótka.


Można również utworzyć inny wykres konwersji, który umożliwia podłączenie do niego typu i określenie sposobu dotarcia do innego typu, prawdopodobnie za pośrednictwem jakiegoś popularnego pośrednika.

Lub można użyć języka skryptowego lub kodu bajtowego, który zawiera pełny kompilator podczas fazy wykonywania, umożliwiając kompilację typu usuniętej metody do nowego całkowicie niezależnego typu, gdy zostanie wywołany.


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

Można wymienić unordered_map z kompaktowy stos na bazie jednego, gdy jest mała. Zauważ, że std::function w MSVC jest ograniczony do około 64 bajtów?


Jeśli nie chcesz mieć ustalonej listy typów źródeł/danych, możemy ją rozdzielić.

  • wystawiać typeindex typu przechowywanej w pojemniku typu wymazywania i zdolność do uzyskania w void const*, który wskazuje na to.

  • Utwórz cechę, która odwzorowuje typ T na listę typów Us... obsługuje konwersję do. Użyj powyższej techniki, aby zapisać te funkcje konwersji w mapie (globalnej). (Zauważ, że ta mapa może być umieszczona w pamięci statycznej, ponieważ możesz wydedukować rozmiar wymaganego bufora itp. Ale użycie numeru static unordered_map jest łatwiejsze).

  • Utwórz drugą cechę, która odwzorowuje typ U na listę typów Ts..., która obsługuje konwersję z.

  • W obu przypadkach wywoływana jest funkcja convert_construct(T const* src, tag_t<U>, void* dest) w celu dokonania rzeczywistej konwersji.

Zaczynasz od zestawu uniwersalnych celów type_list<int, std::string, whatever>. Konkretny typ zwiększyłby go poprzez posiadanie nowej listy.

Dla typu T budującego swoją tabelę rzadkich konwersji spróbujemy każdy typ celu. Jeśli nie zostanie znalezione przeciążenie convert_construct, mapa nie zostanie wypełniona dla tego przypadku. (Generowanie błędów czasu kompilacji dla typów dodanych jawnie do pracy z T jest opcją).

Na drugim końcu, kiedy nazywamy type_erased_convert_to<U>(from), szukamy inny tabeli, która mapuje typ U krzyż typeindex do U(*)(void const* src) konwertera. Zarówno z mapy T otrzymanej z usuniętego typu T i do-U w kodzie owijania są konsultowane, aby znaleźć konwerter.

To nie pozwala na pewne rodzaje konwersji. Na przykład typ T, który konwertuje ze wszystkich metod za pomocą metody .data() -> U* i .size() -> size_t, musi jawnie wymieniać każdy typ, z którego konwertuje.

Następnym krokiem będzie dopuszczenie konwersji wieloetapowej. Wielokrokowa konwersja polega na uczeniu konwersji na niektóre (znane) typy i uczymy się przekształcać - z podobnego (zestawu) znanych typów. (Sława tych typów jest opcjonalna, muszę przyznać, wszystko, co musisz wiedzieć, to jak je tworzyć i niszczyć, jakie miejsce jest potrzebne i jak dopasować T -do i U -z opcji, do wykorzystania ich jako pośrednika.)

Może wydawać się, że są one nadinterpretowane. Ale możliwość konwersji do std::int64_t i konwersji z tego na dowolny podpisany typ całki jest tego przykładem (i podobnie dla uint64_t i bez znaku).

Możliwość konwertowania na słownik par klucz-wartość, a następnie sprawdzenie tego słownika po drugiej stronie w celu ustalenia, czy możemy go przekonwertować.

Idąc tą ścieżką, należy sprawdzić luźne systemy pisania w różnych językach skryptów i kodów bajtowych, aby dowiedzieć się, jak to zrobili.

+0

Mimo że jest to świetna propozycja, nie jestem pewien, czy miałoby to zastosowanie do bieżącego problemu. Zrobiłem małą zmianę, aby podkreślić problem, na który natrafiłem; jest to, że funkcja 'convert' nie ma żadnej wiedzy o' T'. Jedyną funkcją w powyższym przykładzie jest 'build_vtable', w przeciwnym razie typ jest po prostu" void * ". – Bitwize

+0

Chociaż wydaje mi się, że jest to mniej prawdopodobne, początkowo miałem nadzieję, że uda mi się to osiągnąć, nie wymagając przydziałów sterty (takich jak używanie mapy). Chociaż przypuszczam, że jest to statyczne, można to zrobić za pomocą natrętnej listy par ('std :: type_index', function ptr). – Bitwize

+0

@Bitwize Tak, 'type_erased_convert' nie ma wiedzy o T w powyższej sugestii. Operacja usunięcia typu to '(void *) ->' map z 'typeindex' na' std :: function 'Rozproszona mapa nie musi być przydzielana do sterty. – Yakk