2017-09-06 63 views
7

假设我有一些泛型代码,我希望为实现相同底层功能但具有不同成员函数名称的接口的多个类重用。例如,如果基础类具有erase成员函数(例如, std::setstd::unordered_set编译时在C++中使用别名成员函数

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; 
} 

但是,现在我希望此功能可以与例如tbb::concurrent_unordered_set,它提供了一个名为unsafe_erase的函数。

我最初的做法是利用部分模板专业化的类型特征,通过定义以下内容,并调用set_ops<T>::erase(set, v)来代替。不幸的是,这不会编译,因为 tbb::concurrent_unordered_set是一个模板类,而不是一个类型。我还尝试使用第二个键类型的模板参数来扩展类型特征,但由于T不是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); 
}; 

我也尝试用函数模板包装成员函数,如下所示。这似乎是编译的,但由于未定义的引用而未能链接。 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()); 

我应该如何在编译时执行此别名?我知道我可以提供从每个基础类的抽象接口继承的实现,或者使用指向成员函数的指针,但是我想避免任何运行时间开销。

回答

3

您可以直接在你的帮手结构提供简单的包装功能与偏特一起:

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); 
    } 
}; 

然后你set_inert_time功能会是这个样子:

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; 
} 

这样就避免了所有搞乱使用成员函数指针,并且在编译时保持一切都很好解析。

+1

似乎过于复杂。两个版本的'erase'可以是自由函数,通过重载分辨率来选择。 – MSalters

+0

@MSalters这是ether部分专业化或'enable_if' SFINE技巧。否则,最终会出现'erase(tbb :: concurrent_unordered_set &,const tbb :: concurrent_unordered_set :: value_type&)'的模糊重载。 –

0

只要成员函数具有统一的签名,就可以使用指向成员函数的指针,既可以作为非类型的模板参数,也可以作为编译时间constexpr,但语法可能是......您知道,它是无论如何C++。

以下代码为gcc 7.1编译。我没有tbb库来测试它,但它应该适用于其他编译器。

// 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; 
}; 

编辑:

忘记成员函数指针疯狂。

查看Miles的答案。一个非成员函数包装器肯定是一种更干净的方式。

1

如果编译器已经实现了概念TS,它可能是那样简单:

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; 
} 

而且你可以做的是通过实例化的模板函数之前检查的概念更好。

+0

很高兴知道,但概念TS在实践中有点太新了。 – ddcc

+0

@ddcc我有种被新事物吸引的感觉,特别是甚至在使用它们的时候,使用它们解决老旧的静音模板代码!工具的质量并不在其年代,而在于其提供的杠杆作用!其次,在一年之内,这个答案的前景将会减少。 – Oliv

1

你可以简单地使用过载一些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); }); 
} 
+0

这也适用,虽然现在调用图由于lambda函数而被反转,而'timed_func'作为被调用者而不是调用者。 – ddcc

相关问题