In C++, I have a class that contains two members which are sorted containers. The containers store the same object type, but one is sorted in ascending order and the other in descending order. Note: the object type in each container is the same, but the actual object instances stored in the two containers are different. In other words, the data is not duplicated.
I have several template methods which visit items in a container, with the template argument saying which container to visit (i.e. the one sorted in ascending order, or the other).
Regardless of which container I iterate on, I perform the exact same operations. But the order in which I visit the items is important.
So I'm looking for an abstraction or language construct that allows me to work with the two containers in a generic way.
Here is an example to help explain what I'm trying to do:
#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;
}
The key part of the example is the printSet()
method. The commented-out code gives a compiler error ("operands to ‘?:’ have different types"), but shows the intent of what I'd like to do. Imagine this function wasn't trivial, but instead had a fair amount of logic to perform on each item in the set. I'd rather not copy and paste the code as I did in the example.
So I feel like there should be some kind of abstraction or language feature I can use to achieve the intended result, but I'm not sure what that is.
Note: I am forced to use C++14, so any solution that uses newer language features won't work for me.
Edit: I added some code to the example above. The compiler does not complain about using a fwd_set_t::iterator to also refer to a rev_set_t::iterator. And calling printSetAlternate()
works as expected. My question is, is this safe to do? I would like to combine this with the suggestion from @NathanOliver below. The actual use-case involves passing iterators around to different functions.
You can write a couple private helper functions and tags like
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_; }
and then use tag dispatch to call the one to get the correct set like
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";
}
If you can upgrade to C++17, you could removes the tags and the overloads and change getSet
to just be
template <bool IS_FWD> inline auto& getSet() const
{
if constexpr(IS_FWD)
return fwd_set;
else
return rev_set;
}
and then printSet
would become
template <bool IS_FWD> inline void printSet() const
{
auto const& s = getSet();
for (auto const& n : s) { std::cout << n << " "; }
std::cout << "\n";
}