I'm trying to pass an object to boost::adaptors::transformed
. However, this only seems to work if that object's class defines const
versions of begin
and end
. This is not the case for me however, because iterating over an object of this class modifies internal state of the object itself. Here is a minimal example using a dummy class Vector
that only exposes non the const versions begin
and end
or its _v
member, what can I change to make this work?
#include <initializer_list>
#include <vector>
#include <boost/range/adaptors.hpp>
template<typename T>
class Vector
{
public:
using value_type = typename std::vector<T>::value_type;
using reference = typename std::vector<T>::reference;
using iterator = typename std::vector<T>::iterator;
Vector(std::initializer_list<T> init)
: _v(init)
{}
iterator begin() { return _v.begin(); }
iterator end() { return _v.end(); }
private:
std::vector<T> _v;
};
int main()
{
Vector<int> v{1, 2, 3, 4};
auto t = [](int i){ return 2 * i; };
auto range(v | boost::adaptors::transformed(t)); // does not compile
}
I'd say in general it's a code smell that iteration modifies a collection.
Of course, something can be logically const, which is what we have the mutable
keyword for. There's roughly two approaches I can see
Keep in mind that threading-aware libraries might assume that
const
operations are threadsafety guarantees (so either bit-wise immutable OR e.g. only operation on synchronization primitives like a member mutex).
#include <initializer_list>
#include <vector>
#include <boost/range/adaptors.hpp>
#include <fmt/ranges.h>
using boost::adaptors::transformed;
template<typename T>
class Vector {
using Cont = std::vector<T>;
public:
using value_type = typename Cont::value_type;
using reference = typename Cont::reference;
using iterator = typename Cont::iterator;
using const_iterator = typename Cont::const_iterator;
Vector(std::initializer_list<T> init) : _v(init) {}
iterator begin() { return _v.begin(); }
iterator end() { return _v.end(); }
const_iterator begin() const { return _v.begin(); }
const_iterator end() const { return _v.end(); }
//const_iterator cbegin() const { return _v.begin(); }
//const_iterator cend() const { return _v.end(); }
private:
Cont mutable _v;
};
static auto twice(int i) { return 2 * i; }
int main() {
fmt::print("{} -> {}\n",
Vector {1, 2, 3, 4},
Vector {1, 2, 3, 4} | transformed(twice));
}
Prints
{1, 2, 3, 4} -> {2, 4, 6, 8}
For fun, let's make an Element that tracks the number of times its value was observed:
#include <initializer_list>
#include <vector>
#include <boost/range/adaptors.hpp>
#include <fmt/ranges.h>
using boost::adaptors::transformed;
struct Element {
Element(int value) : value(value) {}
operator int() const { ++usage_counter; return value; }
long usages() const { return usage_counter; }
private:
mutable long usage_counter = 0;
int value;
};
template<typename T>
class Vector {
using Cont = std::vector<T>;
public:
using value_type = typename Cont::value_type;
using reference = typename Cont::reference;
using iterator = typename Cont::iterator;
using const_iterator = typename Cont::const_iterator;
Vector(std::initializer_list<T> init) : _v(init) {}
iterator begin() { return _v.begin(); }
iterator end() { return _v.end(); }
const_iterator begin() const { return _v.begin(); }
const_iterator end() const { return _v.end(); }
//const_iterator cbegin() const { return _v.begin(); }
//const_iterator cend() const { return _v.end(); }
private:
Cont _v;
};
static auto twice(int i) { return 2 * i; }
int main() {
Vector<Element> const v {1, 2, 3, 4}; // note const
fmt::print("{} -> {} (usages {})\n",
v,
v | transformed(twice),
v | transformed(std::mem_fn(&Element::usages))
);
}
Prints
{1, 2, 3, 4} -> {2, 4, 6, 8} (usages {3, 3, 3, 3})