2017-09-06 82 views
7

Załóżmy, że mam pewien kod generyczny, który chciałbym ponownie użyć dla wielu klas, które implementują tę samą podstawową funkcjonalność, ale mają interfejsy z różnymi nazwami funkcji składowych. Na przykład poniższy kod będzie działał, jeśli podstawowa klasa ma funkcję składową erase, np. std::set lub std::unordered_set.Alias ​​funkcji członka w C++ w czasie kompilacji

template <typename T> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    set.erase(v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

Ale teraz chcę, aby ta funkcja działała z np. tbb::concurrent_unordered_set, który zapewnia zamiast tego funkcję o nazwie unsafe_erase.

Moje początkowe podejście polegało na wykorzystaniu cech typu z częściową specjalizacją szablonu, poprzez zdefiniowanie poniższych i wywołanie set_ops<T>::erase(set, v). Niestety to się nie kompiluje, ponieważ tbb::concurrent_unordered_set jest klasą szablonową, a nie typem. Próbowałem również rozszerzyć cechę typu o drugi argument szablonu dla typu klucza, ale nie udało się skompilować, ponieważ T nie jest szablonem w std::mem_fn(&T<U>::erase).

template <typename T> 
struct set_ops { 
    constexpr static auto erase = std::mem_fn(&T::erase); 
}; 

template <> 
struct set_ops<tbb::concurrent_unordered_set> { 
    constexpr static auto erase = std::mem_fn(&T::unsafe_erase); 
}; 

Próbowałem również owijać funkcję składającą się z szablonu funkcji w następujący sposób. Wydaje się, że to kompiluje, ale nie łączy się z powodu niezdefiniowanych odwołań do np. decltype ((({parm#1}.erase)({parm#2})),((bool)())) erase<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >(std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >&, std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >::key_type const&)

template <typename T> 
constexpr auto set_erase(T& s, const typename T::key_type &v) -> decltype(s.erase(v), bool()); 
template <typename T> 
constexpr auto set_erase(T& s, const typename T::key_type &v) -> decltype(s.unsafe_erase(v), bool()); 

Jak wykonać to aliasing w czasie kompilacji? Wiem, że mógłbym zapewnić implementację, która dziedziczy po abstrakcyjnym interfejsie dla każdej klasy bazowej lub wykorzystywać wskaźnik do funkcji składowej, ale chciałbym uniknąć jakiegokolwiek nakładu pracy w czasie wykonywania.

Odpowiedz

3

Można tylko dostarczać prostych funkcji otoki w swoich strukturach pomocniczych wraz z częściową specjalizację:

template <typename T> 
struct set_ops { 
    static auto erase(T& t, const T::value_type& obj) { 
    return t.erase(obj); 
    } 
}; 

template <typename... T> 
struct set_ops<tbb::concurrent_unordered_set<T...>> { 
    using set_type = tbb::concurrent_unordered_set<T...>; 
    static auto erase(set_type& t, const typename set_type::value_type& obj) { 
    return t.unsafe_erase(obj); 
    } 
}; 

Następnie czynność set_inert_time będzie wyglądać mniej więcej tak:

template <typename T> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    set_ops<T>::erase(set, v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

ten sposób unika wszystkich Messing wokół z wskaźnikami funkcji członkowskich i pozostawia wszystko ładnie rozwiązalne podczas kompilacji.

+1

Wydaje niepotrzebnie skomplikowane. Dwie wersje 'erase' mogą być wolnymi funkcjami, wybieranymi przez rozdzielczość przeciążenia. – MSalters

+0

@MSalters To jest częściowa specjalizacja ethernetowa lub 'enable_if' triki SFINE. W przeciwnym razie kończy się to niejednoznacznymi przeciążeniami dla 'erase (tbb :: concurrent_unordered_set &, const tbb :: concurrent_unordered_set :: value_type &)'. –

0

tak długo, jak funkcje składowe mają umundurowaną sygnaturę, można użyć wskaźnika do funkcji składowej, albo jako parametru szablonu nie typu lub jako constexpr kompilacji, ale składnia może być ... wiesz, to jest C++ w każdym razie.

następujący kod kompiluje dla gcc 7.1. Nie mam biblioteki Tbb, aby ją przetestować, ale powinna działać dla innych kompilatorów.

// traits declaration 
template <typename T> struct set_ops; 

// this template use a non type template parameter, assuming the member 
// function's signature is like this: (pseudo code) 
// template <typename T> struct some_set_implementation<T> 
// { iterator_type erase(const value_type &); }; 
template <typename T, typename T::iterator_type (T::*memfn)(const typename T::value_type &) = set_ops<T>::erase> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    (set.*memfn)(v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

// this code use constexpr 
template <typename T> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    constexpr auto memfn = set_ops<T>::erase; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    (set.*memfn)(v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

// here goes specilizations for the type trait 
template <typename T> 
struct set_ops<concurrent_unordered_set<T>> { 
    static constexpr auto erase = &concurrent_unordered_set<T>::unsafe_erase; 
}; 
template <typename T, template <typename> class CONTAINER> 
struct set_ops<CONTAINER<T>> { 
    static constexpr auto erase = &CONTAINER<T>::erase; 
}; 

EDIT:

zapomnieć o funkcji członka wskaźnik szaleństwa.

zobacz odpowiedź Milesa. opakowanie nie będące elementem funkcji zdecydowanie jest czystszym sposobem.

1

Jeśli kompilator wdrożył Koncepcja TS, to może być tak proste, jak:

template <typename T> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    if constexpr(requires{set.erase(v);}) set.erase(v); 
    else set.unsafe_erase(v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

I można zrobić lepiej, sprawdzając koncepcję zanim funkcja szablon jest tworzony.

+0

Dobrze wiedzieć, ale koncepcja TS jest nieco zbyt nowa, aby można ją było wykorzystać w praktyce. – ddcc

+0

@ddcc Przyciągają mnie nowe rzeczy, zwłaszcza gdy są nawet naśladujące, używając ich, rozwiązują stary, nieporęczny kod błędu !! Jakość narzędzia nie mieści się w jego wieku, ale w dźwigni, którą zapewnia! Po drugie, w ciągu jednego roku ta odpowiedź będzie mniej perspektywiczna. – Oliv

1

Może wystarczy skorzystać z jakiegoś przeciążenia SFINAE:

template <typename F> 
static std::chrono::duration<double> timed_func(F&& f) { 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    std::forward<F>(f)(); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 


template <typename T> 
static auto set_insert_time(const typename T::value_type &v) 
-> decltype(
    static_cast<void>(std::declval<T&>().erase(v)), 
    std::declval<std::chrono::duration<double>>()) 
{ 
    T set; 
    return timed_func([&](){ set.erase(v); }); 
} 

template <typename T> 
static auto set_insert_time(const typename T::value_type &v) 
-> decltype(
    static_cast<void>(std::declval<T&>().unsafe_erase(v)), 
    std::declval<std::chrono::duration<double>>()) 
{ 
    T set; 
    return timed_func([&](){ set.unsafe_erase(v); }); 
} 
+0

Działa to również, chociaż wykres wywołania jest teraz odwrócony z powodu funkcji lambda, z 'timed_func' jako wywoływacz zamiast wywołującego. – ddcc