假设我有一些通用代码,我希望将其重用于实现相同底层功能的多个类,但具有不同成员函数名的接口。例如,如果基础类具有erase
成员函数,则以下代码将起作用,例如, std::set
或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;
}
但是,现在我希望这个功能可用于例如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());
我应该如何在编译时执行此别名?我知道我可以提供一个继承自每个底层类的抽象接口的实现,或者使用指向成员函数的指针,但我想避免任何运行时开销。
答案 0 :(得分: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 :(得分: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;
}
通过在实例化模板函数之前检查概念,你可以做得更好。
答案 2 :(得分: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); });
}
答案 3 :(得分: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;
};
编辑:
忘记成员函数指针疯狂。
见迈尔斯的回答。一个非成员函数包装器肯定是一种更清洁的方式。