Chris Stathis Chris Stathis - 3 months ago 13
C++ Question

std::upper_bound returns const iterator in const member function

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.

Answer

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.