Highlight member function in C ++ at compile time

Suppose I have common code that I would like to reuse for several classes that implement the same basic functions but have interfaces with different member function names. For example, the following code will work if the base class has an erase member erase , for example. 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, for example. tbb::concurrent_unordered_set , which instead performs a function called unsafe_erase .

My initial approach was to use type properties with a partial template specialization, defining the following and calling set_ops<T>::erase(set, v) . Unfortunately, this does not compile because tbb::concurrent_unordered_set is a template, not a type. I also tried extending the type property with the second template argument for the key type, but this does not compile because T 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 wrapping a member function with a function template as follows. This seems to be compiling but not linking due to undefined links, for example. 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 do I do this overlay at compile time? I know that I could provide an implementation that inherits from an abstract interface for each base class, or use a pointer to a member function, but I would like to avoid any extra time.

+7
c ++ templates metaprogramming
source share
4 answers

You can simply provide simple wrapper functions in your helper structures along with a 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 will 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 the clutter with member function pointers and leaves everything well solvable at compile time.

+3
source share

If your compiler has implemented the concept of TS, it could be so simple:

 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 can do it better by checking out the concept before the template function is created.

+1
source share

You can simply use overloading 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); }); } 
+1
source share

since member functions have a unified signature, you can use a pointer to a member function either as an asymmetric template parameter or as constexpr compilation time, but the syntax can be ... you know, this is C ++ anyway.

The following code compiles for gcc 7.1. I don't have a tbb library to test it, 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 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 frenzy of a member function pointer.

see Miles answer. non-member wrapper functions are definitely a cleaner way to go.

0
source share

All Articles