ddcc
ddcc

Reputation: 549

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.

Upvotes: 8

Views: 1794

Answers (4)

Miles Budnek
Miles Budnek

Reputation: 30579

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.

Upvotes: 3

Jarod42
Jarod42

Reputation: 217810

You might simply use overload with some 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); });
}

Upvotes: 1

Oliv
Oliv

Reputation: 18081

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.

Upvotes: 1

书呆彭
书呆彭

Reputation: 101

as long the member functions have a uniformed signature, you can use a pointer to member function, either as a non-type template parameter or as a compile time constexpr, but the syntax might be... you know, it's C++ anyway.

the following code compiles for gcc 7.1. I don't have the tbb library to test it out, but it should work for other compilers.

// 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:

forget about the member function pointer madness.

see the answer by Miles. a non-member function wrapper definitely is a cleaner way to go.

Upvotes: 0

Related Questions