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.
Wydaje niepotrzebnie skomplikowane. Dwie wersje 'erase' mogą być wolnymi funkcjami, wybieranymi przez rozdzielczość przeciążenia. – MSalters
@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 &)'. –