Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alias a member function in C++ at compile time

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.

like image 416
ddcc Avatar asked Sep 06 '17 03:09

ddcc


2 Answers

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.

like image 170
Miles Budnek Avatar answered Sep 19 '22 13:09

Miles Budnek


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.

like image 35
Oliv Avatar answered Sep 17 '22 13:09

Oliv