模板化有序向量和 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(),val);
            // ... or do a clever std::rotate() to get it in place.
            return it;
        }
    }
    
private:
    C data;
};