Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I ease the syntactic overhead of checking iterator values in C++?

Tags:

c++

iterator

stl

Basically, I'm a bit tired of writing:

std::map<key_t, val_t> the_map;
...
auto iterator = the_map.find(...);
if(iterator != the_map.end()) { // note the "inversed" logic and logically superflous end() call
  ...
}

What really made sense would be:

if(auto x=the_map.find(...)) {
  ... // x could either be an iterator or maybe something like boost::optional<val_t>
}

Is there some prior art that defines some helper stuff to shorten the != container.end() syntax or am I the only one annoyed by this?

like image 481
Martin Ba Avatar asked Oct 24 '11 14:10

Martin Ba


4 Answers

You could write a class template auto_iterator_impl and use it through a function template auto_iterator which returns an instance of auto_iterator_impl, which can be implicitly converted into true or false:

A working implementation with minimal functionalities and consideration:

template<typename C>
struct auto_iterator_impl
{
    C & c;
    typename C::iterator  it;
    auto_iterator_impl(C & c,  typename C::iterator & it) : c(c), it(it) {}
    operator bool() const { return it != c.end(); }
    typename C::iterator operator->() { return it; }
};

template<typename C>
auto_iterator_impl<C> auto_iterator(C & c, typename C::iterator  it)
{
    return auto_iterator_impl<C>(c, it);
}

Test code:

void test(std::map<int, int> & m, int key)
{
    if (auto x = auto_iterator(m, m.find(key)))
    {
        std::cout << "found = " << x->second << std::endl;
        x->second *= 100; //change it
    }
    else
        std::cout << "not found" << std::endl;
}

int main()
{
    std::map<int,int> m;
    m[1] = 10;
    m[2] = 20;  
    test(m, 1);
    test(m, 3);
    test(m, 2);

    std::cout <<"print modified values.." <<std::endl;
    std::cout << m[1] << std::endl;
    std::cout << m[2] << std::endl;
}

Output:

found = 10
not found
found = 20
print modified values..
1000
2000

Online demo : http://www.ideone.com/MnISh

like image 159
Nawaz Avatar answered Oct 24 '22 07:10

Nawaz


Well, if it means that much to you, how about a little wrapper:

template <typename Container>
inline bool find_element(Container const & c,
                         typename Container::const_iterator & it,
                         typename Container::value_type const & val)
{
  return (it = c.find(val)) == c.end();
}

Usage:

std::vector<int> v;
std::vector<int>::const_iterator it;

if (find_element(v, it, 12)) { /* use it */ }
like image 38
Kerrek SB Avatar answered Oct 24 '22 06:10

Kerrek SB


I think it's the most flexible decision to write a wrapper like

template<class Iterable>
bool CheckIterator(const typename Iterable::iterator& iter, const Iterable& collection)
{
    return !(iter == collection.end());
}

and use it

    map<int,string> m;
    m[1]="hello";
    m[2]="world";
    map<int,string>::iterator it = m.find(2);
    if(CheckIterator(it,m))
    {
        cout<<it->second;
    } 

it may be used with another types of containers (e.g. vector's) as well

like image 1
VinS Avatar answered Oct 24 '22 07:10

VinS


How about this?

BOOST_FOREACH(auto &x : c.equal_range(...)) {
    // x is a val_t &, or const if c is const.
}

I'm assuming that if it's OK for x to be boost::optional<val_t>, then you don't actually need the iterator, just the value, so a reference is OK.

You can use a C++11 range-based for loop instead of BOOST_FOREACH if you use a boost::iterator_range instead of a std::pair (which is what equal_range returns).

like image 1
Steve Jessop Avatar answered Oct 24 '22 06:10

Steve Jessop