仅排序功能不同的已排序容器的 C++ 抽象?

A C++ abstraction for sorted containers that differ only in sort function?

在 C++ 中,我有一个 class,其中包含两个成员,它们是已排序的容器。容器存储相同的对象类型,但一个按升序排序,另一个按降序排序。注意:每个容器中的对象type是相同的,但是两个容器中实际存储的对象instances是不同的。也就是说,数据没有重复。

我有几个访问容器中项目的模板方法,模板参数说明要访问哪个容器(即按升序排序的容器,或另一个)。

无论我迭代哪个容器,我都执行完全相同的操作。但是我访问项目的顺序很重要。

所以我正在寻找一种抽象或语言结构,使我能够以通用方式使用这两个容器。

这里有一个例子可以帮助解释我正在尝试做的事情:

#include <set>
#include <iostream>

class MySets
{
    public:
        using fwd_set_t = std::set<int, std::less<int>>;
        using rev_set_t = std::set<int, std::greater<int>>;

        // BEGIN: added in edit
        using MyIterator = fwd_set_t::iterator;
        using MyConstIterator = fwd_set_t::const_iterator;

        // is this safe??? the compiler isn't complaining that I'm using
        // fwd_set_t::iterator and rev_set_t::iterator interchangeably
        template <bool IS_FWD> inline MyConstIterator begin() const
        {
            return (IS_FWD ? fwd_set_.begin() : rev_set_.begin());
        }

        template <bool IS_FWD> inline MyConstIterator end() const
        {
            return (IS_FWD ? fwd_set_.end() : rev_set_.end());
        }

        template <bool IS_FWD> inline void printSetAlternate() const
        {
            for (auto iter = begin<IS_FWD>(); iter != end<IS_FWD>(); ++iter)
            {
                std::cout << "  " << (*iter) << "\n";
            }
        }
        // END: added in edit


        MySets()
            : fwd_set_({10, 20, 30, 40, 50})
            , rev_set_({11, 21, 33, 44, 55})
            {
            }

        template <bool IS_FWD> inline void printSet() const
        {
            //auto const& s = (IS_FWD ? fwd_set_ : rev_set_); // ERROR - different types
            //for (auto const& n : s) { std::cout << "  " << n << "\n"; }

            if (IS_FWD) { for (auto const& n : fwd_set_) { std::cout << "  " << n << "\n"; } }
            else        { for (auto const& n : rev_set_) { std::cout << "  " << n << "\n"; } }
        }

    private:
        fwd_set_t fwd_set_;
        rev_set_t rev_set_;
};

int main(int argc, char* argv[])
{
    MySets ms;

    std::cout << "fwd:\n";
    ms.printSet<true>();

    std::cout << "rev:\n";
    ms.printSet<false>();

    return 0;
}

示例的关键部分是 printSet() 方法。注释掉的代码给出了一个编译器错误(“'?:' 的操作数有不同的类型”),但显示了我想做的事情的意图。想象一下这个函数不是微不足道的,而是有相当多的逻辑要对集合中的每个项目执行。我不想像示例中那样复制和粘贴代码。

所以我觉得应该有某种抽象或语言功能可以用来实现预期的结果,但我不确定那是什么。

注意:我被迫使用 C++14,因此任何使用较新语言功能的解决方案都不适合我。

编辑: 我在上面的示例中添加了一些代码。编译器不会抱怨使用 fwd_set_t::iterator 也引用 rev_set_t::迭代器。并调用 printSetAlternate() 按预期工作。我的问题是,这样做安全吗?我想将此与下面@NathanOliver 的建议结合起来。实际用例涉及将迭代器传递给不同的函数。

您可以编写一些私有辅助函数和标签,例如

struct fwd_tag_t{};
struct rev_tag_t{};
auto& getSet(fwd_tag_t) const { return fwd_set_; }
auto& getSet(rev_tag_t) const { return rev_set_; }

然后使用标签分派来调用一个以获得正确的集合,如

template <bool IS_FWD> inline void printSet() const
{
    auto const& s = getSet(std::conditional_t<IS_FWD, fwd_tag_t, rev_tag_t>{});
    for (auto const& n : s) { std::cout << n << " "; }
    std::cout << "\n";
}

如果可以升级到 C++17,则可以删除标签和重载并将 getSet 更改为

template <bool IS_FWD> inline auto& getSet() const
{
    if constexpr(IS_FWD) 
        return fwd_set;
    else 
        return rev_set;
}

然后 printSet 会变成

template <bool IS_FWD> inline void printSet() const
{
    auto const& s = getSet();
    for (auto const& n : s) { std::cout << n << " "; }
    std::cout << "\n";
}

另一种选择,取决于您要用它做什么,是将数据保留一次,然后使用 reverse_iterator

void printSet(bool is_fwd)
{
    auto print = [](auto first, auto last) {
       while(first != last) {  std::cout << *first++ << "\n"; }
    };
    if (is_fwd) {
      print(fwd_set_.begin(), fwd_set.end());
    }  else {
      print(fwd_set.rbegin(), fwd_set.rend());
    }
}

随意制作 printSet 模板,并使用 if constexpr 或标签调度。您还可以使 lambda 成为一个单独的成员函数