模板化有序向量和 std::set
Templating ordered vector and std::set
所以我想要一个接口,可以按顺序存储一些数字并有效地对它们执行一些操作。在实践中,排序向量对于小尺寸表现非常好,但对于理论性能和更大实例的性能,我还需要一个变体,它使用类似 std::set
的东西在 O(log n)
时间内执行插入和擦除。
这里是向量变化的一些实现:
#include <vector>
#include <algorithm>
class Numbers {
public:
template<class ForwardIt>
void unguarded_insert(ForwardIt it, int val) { vec.insert(it, val); }
auto lower(int val) { return std::lower_bound(vec.begin(), vec.end(), val); }
template<class ForwardIt>
void unguarded_replace(ForwardIt it, int val) { *it = val; }
template<class ForwardIt>
auto erase_elements(ForwardIt first, ForwardIt last) { return vec.erase(first, last); }
private:
std::vector<int> vec;
};
请注意,对于插入和替换它,调用者确保它们位于正确的位置。例如,调用者可能想删除 20 到 50 之间的所有元素并插入 30。因此可以调用 lower_bound
,替换第一个元素并删除剩余范围。
我不想将整个界面写两次,而是想要一个使用容器类型作为模板参数的模板化解决方案。但是,我 运行 遇到了诸如 lower_bound
是某些容器的成员函数和其他容器的自由函数之类的问题。 std::set
也不能真正在迭代器上插入和替换,因为顺序被不必要地检查了(对吧?)。
那么有什么方法可以解决这些问题,或者有其他方法可以解决我的问题吗?该解决方案不应在任何一个容器上做不必要的工作,只是为了让它在两个容器上都能工作。
您可能不需要多次实现所有成员函数,但对于某些成员函数,您需要以不同的方式实现。
首先,检查 lower_bound
成员函数是否存在的类型特征
template<class T>
struct has_lower_bound_member_func {
struct no {};
template<class U = T> // on SFINAE match: `auto` is the type of the iterator
static auto check(int) ->
decltype(std::declval<U>().lower_bound(
std::declval<typename U::value_type>()));
static no check(long); // No lower_bound found, the type is `no`
// if the type is *not* `no`, the container has a lower_bound member function
static constexpr bool value = not std::is_same_v<decltype(check(0)), no>;
};
template<class T>
inline constexpr bool has_lower_bound_member_func_v =
has_lower_bound_member_func<T>::value;
检查迭代器是否为 const_iterator
:
的类型特征
template<class It>
struct is_const_iterator {
using pointer = typename std::iterator_traits<It>::pointer;
static const bool value = std::is_const_v<std::remove_pointer_t<pointer>>;
};
template<class T>
inline constexpr bool is_const_iterator_v = is_const_iterator<T>::value;
以下是如何使用这些特征(以及您添加的任何未来特征)的几个示例。您可以对其他需要特别注意的成员函数使用类似的技术:
template<class C>
class Numbers {
public:
using value_type = typename C::value_type;
template<class T = C>
auto lower(const value_type& val) {
if constexpr (has_lower_bound_member_func_v<T>) {
// a std::set<> will use this
return data.lower_bound(val);
} else {
// a std::vector<> will use this
return std::lower_bound(data.begin(), data.end(), val);
}
}
template<class ForwardIt>
auto replace(ForwardIt it, const value_type& val) {
if constexpr(is_const_iterator_v<ForwardIt>) {
// move the object out of the container by moving its node
// out of the set and reinsert if afterwards:
auto node_handle = data.extract(it);
node_handle.value() = val;
return data.insert(std::move(node_handle));
// or, if "it" is likely pointing close to the old value:
// return data.insert(it, std::move(node_handle));
} else {
*it = val; // not a const_iterator, assign directly
// You probably need to sort here:
std::sort(data.begin(), data.end());
it = std::lower_bound(data.begin(), data.end(), val);
// ... or do a clever std::rotate() to get it in place.
return it;
}
}
private:
C data;
};
所以我想要一个接口,可以按顺序存储一些数字并有效地对它们执行一些操作。在实践中,排序向量对于小尺寸表现非常好,但对于理论性能和更大实例的性能,我还需要一个变体,它使用类似 std::set
的东西在 O(log n)
时间内执行插入和擦除。
这里是向量变化的一些实现:
#include <vector>
#include <algorithm>
class Numbers {
public:
template<class ForwardIt>
void unguarded_insert(ForwardIt it, int val) { vec.insert(it, val); }
auto lower(int val) { return std::lower_bound(vec.begin(), vec.end(), val); }
template<class ForwardIt>
void unguarded_replace(ForwardIt it, int val) { *it = val; }
template<class ForwardIt>
auto erase_elements(ForwardIt first, ForwardIt last) { return vec.erase(first, last); }
private:
std::vector<int> vec;
};
请注意,对于插入和替换它,调用者确保它们位于正确的位置。例如,调用者可能想删除 20 到 50 之间的所有元素并插入 30。因此可以调用 lower_bound
,替换第一个元素并删除剩余范围。
我不想将整个界面写两次,而是想要一个使用容器类型作为模板参数的模板化解决方案。但是,我 运行 遇到了诸如 lower_bound
是某些容器的成员函数和其他容器的自由函数之类的问题。 std::set
也不能真正在迭代器上插入和替换,因为顺序被不必要地检查了(对吧?)。
那么有什么方法可以解决这些问题,或者有其他方法可以解决我的问题吗?该解决方案不应在任何一个容器上做不必要的工作,只是为了让它在两个容器上都能工作。
您可能不需要多次实现所有成员函数,但对于某些成员函数,您需要以不同的方式实现。
首先,检查 lower_bound
成员函数是否存在的类型特征
template<class T>
struct has_lower_bound_member_func {
struct no {};
template<class U = T> // on SFINAE match: `auto` is the type of the iterator
static auto check(int) ->
decltype(std::declval<U>().lower_bound(
std::declval<typename U::value_type>()));
static no check(long); // No lower_bound found, the type is `no`
// if the type is *not* `no`, the container has a lower_bound member function
static constexpr bool value = not std::is_same_v<decltype(check(0)), no>;
};
template<class T>
inline constexpr bool has_lower_bound_member_func_v =
has_lower_bound_member_func<T>::value;
检查迭代器是否为 const_iterator
:
template<class It>
struct is_const_iterator {
using pointer = typename std::iterator_traits<It>::pointer;
static const bool value = std::is_const_v<std::remove_pointer_t<pointer>>;
};
template<class T>
inline constexpr bool is_const_iterator_v = is_const_iterator<T>::value;
以下是如何使用这些特征(以及您添加的任何未来特征)的几个示例。您可以对其他需要特别注意的成员函数使用类似的技术:
template<class C>
class Numbers {
public:
using value_type = typename C::value_type;
template<class T = C>
auto lower(const value_type& val) {
if constexpr (has_lower_bound_member_func_v<T>) {
// a std::set<> will use this
return data.lower_bound(val);
} else {
// a std::vector<> will use this
return std::lower_bound(data.begin(), data.end(), val);
}
}
template<class ForwardIt>
auto replace(ForwardIt it, const value_type& val) {
if constexpr(is_const_iterator_v<ForwardIt>) {
// move the object out of the container by moving its node
// out of the set and reinsert if afterwards:
auto node_handle = data.extract(it);
node_handle.value() = val;
return data.insert(std::move(node_handle));
// or, if "it" is likely pointing close to the old value:
// return data.insert(it, std::move(node_handle));
} else {
*it = val; // not a const_iterator, assign directly
// You probably need to sort here:
std::sort(data.begin(), data.end());
it = std::lower_bound(data.begin(), data.end(), val);
// ... or do a clever std::rotate() to get it in place.
return it;
}
}
private:
C data;
};