Suppose I have some generic code that I'd like to reuse for multiple classes which implement the same underlying functionality, but have interfaces with different member function names. For example, the following code will work if the underlying class has an erase
member function, e.g. std::set
or std::unordered_set
.
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;
}
But, now I want this function to work with e.g. tbb::concurrent_unordered_set
, which provides a function named unsafe_erase
instead.
My initial approach was to utilize type traits with partial template specialization, by defining the following, and calling set_ops<T>::erase(set, v)
instead. Unfortunately, this doesn't compile because
tbb::concurrent_unordered_set
is a templated class and not a type. I also tried to extend the type trait with a second template argument for the key type, but this fails to compile because T
is not a template in 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);
};
I also tried to wrap the member function with a function template, as follows. This seems to compile, but fails to link due to undefined references to e.g. 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());
How should I perform this aliasing at compile time? I know I could provide an implementation that inherits from an abstract interface for each underlying class, or utilize a pointer to a member function, but I'd like to avoid any run-time overhead.
You can just supply simple wrapper functions in your helper structs along with partial specialization:
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);
}
};
Then your set_inert_time
function would look something like this:
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;
}
This avoids all of the messing around with member function pointers, and leaves everything nicely resolvable at compile-time.
If your compiler has implemented the Concept TS, it could be as simple as that:
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;
}
And you could do better by checking the concept before the template function is instantiated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With