std::upper_bound returns const iterator in const member function
Asked Answered
F

2

9

Here is a class that contains a boost::circular_buffer of some struct. I make a typedef for iterators into the contained circular_buffer.

My problem is this: when the doWork function is marked const, the returned value of std::upper_bound is not compatible with the MyIterator type due to the return value having boost::cb_details::const_traits. If I remove the const keyword from the function, all my compile errors go away.

To be clear the compiler error is this:

error: conversion from ‘boost::cb_details::iterator<boost::circular_buffer<Wrapper<int>::Sample, std::allocator<Wrapper<int>::Sample> >, boost::cb_details::const_traits<std::allocator<Wrapper<int>::Sample> > >’ to non-scalar type ‘Wrapper<int>::MyIterator {aka boost::cb_details::iterator<boost::circular_buffer<Wrapper<int>::Sample, std::allocator<Wrapper<int>::Sample> >, boost::cb_details::nonconst_traits<std::allocator<Wrapper<int>::Sample> > >}’ requested    
                          [](const Sample& a, const Sample& b) { return a.foo < b.foo; });

Here is a self-contained example:

#include <algorithm>
#include <boost/circular_buffer.hpp>

template <typename T>
class Wrapper {
 public:
    struct Sample {
        T foo;
    };

    typedef typename boost::circular_buffer<Sample>::iterator MyIterator;

    Wrapper(int size) { cb.resize(size); }

    void add(T val) { cb.push_back(Sample{val}); }

    void doWork(T bound) const {
        MyIterator iter =
            std::upper_bound(cb.begin(), cb.end(), Sample{3},
                         [](const Sample& a, const Sample& b) { return a.foo < b.foo; });
    }

    boost::circular_buffer<Sample> cb;
};

int main() {
    Wrapper<int> buf(100);
    buf.add(1);
    buf.add(5);
    buf.doWork(3);
    return 0;
}

So, why can't this function be const? Why does marking it const have this side-effect? I want a non-const iterator into the container, but in my real test case I don't intend to actually modify the container at all.

Failure answered 30/8, 2016 at 22:50 Comment(5)
Well since doWork is const, cb is treated as const as well. Since doWork does not intend on modifying cb use const_iterator instead.Cancellation
An MCVE! Wonders never cease. +1Edirne
@CaptainObvlious: Answer section is down below, friendEdirne
Why not just use auto iter = std::upper_bound(...?Wellhead
@Wellhead amen to auto, especially since this is tagged c++11Nessy
E
8

You're going to need a const_iterator, since you're effectively observing a const container.

Perhaps:

typedef typename boost::circular_buffer<Sample>::const_iterator MyConstIterator;

… then make iter one of these.

Someone's going to tell you that you could have avoided this with auto. That's true, but then you never would have discovered this "bug", or that const_iterators exist.

Edirne answered 30/8, 2016 at 22:55 Comment(0)
S
4

If your function is marked const then all your access to member variables will be const too.

A const container will only allow access to const_ iterators, that's just the way iterators work.

Skiascope answered 30/8, 2016 at 22:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.