Related
I have little c++ experience, but now I need to look at some code that uses expression templates a lot, so I am reading chapter 18 of the book << C++ Templates: The Complete Guide >> and working on the example provided in the book. If you happened to have the book, the example starts from pp 328, with all the contextual information.
My code works fine until I want to add the support for subvector indexing (pp 338), I could not get the assignment to work, g++ gives the following error:
error: binding ‘const value_type {aka const double}’ to reference of type ‘double&’ discards qualifiers
return v[vi[idx]];
I have no idea what's going on, am I assigning to a constant object? How do I make this work? Here is my code:
#include <iostream>
#include <vector>
template<typename T>
class ET_Scalar {
private:
const T& s;
public:
ET_Scalar(const T& v) :
s(v) {}
T operator[](size_t) const
{
return s;
}
size_t size() const
{
return 0; // Zero means it's a scalar
}
};
template<typename T, typename V, typename VI>
class ET_SubVec {
private:
const V& v;
const VI& vi;
public:
ET_SubVec(const V& a, const VI& b) :
v(a), vi(b) {}
const T operator[] (size_t idx) const
{
return v[vi[idx]];
}
T& operator[] (size_t idx)
{
return v[vi[idx]];
}
size_t size() const
{
return vi.size();
}
};
// Using std::vector as storage
template<typename T, typename Rep = std::vector<T>>
class ET_Vector {
private:
Rep expr_rep;
public:
// Create vector with initial size
explicit ET_Vector(size_t s) :
expr_rep(s) {}
ET_Vector(const Rep& v) :
expr_rep(v) {}
ET_Vector& operator=(const ET_Vector& v)
{
for (size_t i = 0; i < v.size(); i++)
expr_rep[i] = v[i];
return *this;
}
template<typename T2, typename Rep2>
ET_Vector& operator=(const ET_Vector<T2, Rep2>& v)
{
for (size_t i = 0; i < v.size(); i++)
expr_rep[i] = v[i];
return *this;
}
size_t size() const
{
return expr_rep.size();
}
const T operator[](size_t idx) const
{
return expr_rep[idx];
}
T& operator[](size_t idx)
{
return expr_rep[idx];
}
template<typename T2, typename Rep2>
ET_Vector<T, ET_SubVec<T, Rep, Rep2>> operator[](const ET_Vector<T2, Rep2>& vi)
{
return ET_Vector<T, ET_SubVec<T, Rep, Rep2>>(ET_SubVec<T, Rep, Rep2>(expr_rep, vi.rep()));
}
template<typename T2, typename Rep2>
const ET_Vector<T, ET_SubVec<T, Rep, Rep2>> operator[](const ET_Vector<T2, Rep2>& vi) const
{
return ET_Vector<T, ET_SubVec<T, Rep, Rep2>>(ET_SubVec<T, Rep, Rep2>(expr_rep, vi.rep()));
}
// Return what the vector currently represents
const Rep& rep() const
{
return expr_rep;
}
Rep& rep()
{
return expr_rep;
}
};
template<typename T>
class ET_Traits {
public:
typedef const T& ExprRef;
};
template<typename T>
class ET_Traits<ET_Scalar<T>> {
public:
typedef ET_Scalar<T> ExprRef;
};
template<typename T, typename LHS, typename RHS>
class ET_Add {
private:
typename ET_Traits<LHS>::ExprRef lhs;
typename ET_Traits<RHS>::ExprRef rhs;
public:
ET_Add(const LHS& l, const RHS& r) :
lhs(l), rhs(r) {}
T operator[](size_t idx) const
{
return lhs[idx] + rhs[idx];
}
size_t size() const
{
return (lhs.size() != 0) ? lhs.size() : rhs.size();
}
};
template<typename T, typename LHS, typename RHS>
class ET_Mul {
private:
typename ET_Traits<LHS>::ExprRef lhs;
typename ET_Traits<RHS>::ExprRef rhs;
public:
ET_Mul(const LHS& l, const RHS& r) :
lhs(l), rhs(r) {}
T operator[](size_t idx) const
{
return lhs[idx] * rhs[idx];
}
size_t size() const
{
return (lhs.size() != 0) ? lhs.size() : rhs.size();
}
};
// Vector + Vector
template<typename T, typename LHS, typename RHS>
ET_Vector<T, ET_Add<T, LHS, RHS>>
operator+(const ET_Vector<T, LHS>& a, const ET_Vector<T, RHS>& b)
{
return ET_Vector<T, ET_Add<T, LHS, RHS>>(ET_Add<T, LHS, RHS>(a.rep(), b.rep()));
}
// Scalar + Vector
template<typename T, typename RHS>
ET_Vector<T, ET_Add<T, ET_Scalar<T>, RHS>>
operator+(const T& s, const ET_Vector<T, RHS>& b)
{
return ET_Vector<T, ET_Add<T, ET_Scalar<T>, RHS>>(ET_Add<T, ET_Scalar<T>, RHS>(ET_Scalar<T>(s), b.rep()));
}
// Vector .* Vector
template<typename T, typename LHS, typename RHS>
ET_Vector<T, ET_Mul<T, LHS, RHS>>
operator*(const ET_Vector<T, LHS>& a, const ET_Vector<T, RHS>& b)
{
return ET_Vector<T, ET_Mul<T, LHS, RHS>>(ET_Mul<T, LHS, RHS>(a.rep(), b.rep()));
}
//Scalar * Vector
template<typename T, typename RHS>
ET_Vector<T, ET_Mul<T, ET_Scalar<T>, RHS>>
operator*(const T& s, const ET_Vector<T, RHS>& b)
{
return ET_Vector<T, ET_Mul<T, ET_Scalar<T>, RHS>>(ET_Mul<T, ET_Scalar<T>, RHS>(ET_Scalar<T>(s), b.rep()));
}
template<typename T>
void print_vec(const T& e)
{
for (size_t i = 0; i < e.size(); i++) {
std::cout << e[i] << ' ';
}
std::cout << '\n';
return;
}
int main()
{
size_t N = 16;
ET_Vector<double> x(N);
ET_Vector<double> y(N);
ET_Vector<double> z(N);
ET_Vector<int> idx(N / 2);
// Do not use auto z = [expr] here! Otherwise the type of z will still be a
// container, and evaluation won't happen until later. But the compiler
// will optimize necessary information away, causing errors.
z = (6.5 + x) + (-2.0 * (1.25 + y));
print_vec(z);
for (int i = 0; i < 8; i++)
idx[i] = 2 * i;
z[idx] = -1.0 * z[idx];
print_vec(z);
return 0;
}
Sorry about its length, I've failed to create a minimal (not) working example.
Please consider the following (partial) implementation of a mathematical vector class (which is basically the code you can find in the Wikipedia article about expression templates):
namespace math
{
template<class E>
class vector_expression
{
public:
std::size_t size() const {
return static_cast<E const&>(*this).size();
}
double operator[](size_t i) const
{
if (i >= size())
throw std::length_error("");
return static_cast<E const&>(*this)[i];
}
operator E&() { return static_cast<E&>(*this); }
operator E const&() const { return static_cast<E const&>(*this); }
}; // class vector_expression
template<class E1, class E2>
class vector_sum
: public vector_expression<vector_sum<E1, E2>>
{
public:
vector_sum(vector_expression<E1> const& e1, vector_expression<E2> const& e2)
: m_e1(e1), m_e2(e2)
{
if (e1.size() != e2.size())
throw std::logic_error("");
}
std::size_t size() const {
return m_e1.size(); // == m_e2.size()
}
double operator[](std::size_t i) const {
return m_e1[i] + m_e2[i];
}
private:
E1 const& m_e1;
E2 const& m_e2;
}; // class vector_sum
template<typename E1, typename E2>
vector_sum<E1, E2> operator+(vector_expression<E1> const& e1, vector_expression<E2> const& e2) {
return { e1, e2 };
}
template<typename T>
class vector
: public vector_expression<vector<T>>
{
public:
vector(std::size_t d)
: m_data(d)
{ }
vector(std::initializer_list<T> init)
: m_data(init)
{ }
template<class E>
vector(vector_expression<E> const& expression)
: m_data(expression.size())
{
for (std::size_t i = 0; i < expression.size(); ++i)
m_data[i] = expression[i];
}
std::size_t size() const {
return m_data.size();
}
double operator[](size_t i) const { return m_data[i]; }
double& operator[](size_t i) { return m_data[i]; }
private:
std::vector<T> m_data;
}; // class vector
} // namespace math
How should I extend this implementation to allow the following operations:
vector<double> x = { ... };
auto y = 4711 * x; // or y = x * 4711
auto z = 1 + x; // or x + 1, which should yield z[i] = x[i] + 1
I suppose that I need something like
namespace math
{
template<class E, typename T>
class vector_product
: public vector_expression<vector_product<E, T>>
{
public:
vector_product(vector_expression<E> const& e, T const& t)
: m_e(e), m_t(t)
{ }
std::size_t size() const {
return m_e.size();
}
double operator[](std::size_t i) const {
return m_e[i] * m_t;
}
private:
E const& m_e;
T const& m_t;
}; // class vector_product
template<class E, typename T>
vector_product<E, T> operator*(vector_expression<E> const& e, T const& t) {
return { e, t };
}
template<class E, typename T>
vector_product<E, T> operator*(T const& t, vector_expression<E> const& e) {
return e * t;
}
} // namespace math
but I don't know whether or not this is a good approach. So, how should I do it? And should I add any copy or move constructor/assignment operator? I guess not, since the implicit ones should do a perfect job, cause the only member variable of vector is a STL type.
I would simply extend vector_sum to allow for E2 to be double, and to handle that gracefully if it is. This would involve taking arguments of E1 const& and E2 const& in your constructor, potentially not throwing on size differences (since scalars have no size), and rewriting operator[] to not do indexing. For the last part, something like:
double operator[](std::size_t i) const {
return m_e1[i] + get(m_e2, i);
}
private:
template <class E>
double get(E const& rhs, std::size_t i) const {
return rhs[i];
}
double get(double scalar, std::size_t ) const {
return scalar;
}
This way, if you're adding two vector_expressions, you'll do the indexing, but if you're adding a vector_expression and a double - you won't even try to index into the double. This switch happens at compile-time, so there's no run-time overhead.
Then, all you just need to add a couple more operator+s:
template <typename E1>
vector_sum<E1, double> operator+(vector_expression<E1> const& e1, double d) {
return {e1, d};
}
template <typename E1>
vector_sum<E1, double> operator+(double d, vector_expression<E1> const& e1) {
return {e1, d};
}
Which lets you write:
math::vector<int> x = {1, 2, 3, 4};
math::vector<int> y = {2, 3, 4, 5};
auto sum = 3 + x + 1;
Keeping references to const probably isn't the right thing to do - if you did a+b+c, you'll end up keeping a reference to the temporary a+b. You probably only want to keep a reference to the actual vector, and keep copies of all the intermediate objects.
To support vector_product, you'll probably want vector_sum<E1,E2> to really be vector_binary_op<E1,E2,std::plus<>> and then vector_product<E1,E2> should be vector_binary_op<E1,E2,std::multiplies<>>. That way, you won't have all the duplication.
Inspired by Yakk and Barry, I've finally come up with the following:
namespace math
{
template<class E>
class expression
{
public:
auto size() const {
return static_cast<E const&>(*this).size();
}
auto operator[](std::size_t i) const
{
if (i >= size())
throw std::length_error("");
return static_cast<E const&>(*this)[i];
}
operator E&() { return static_cast<E&>(*this); }
operator E const&() const { return static_cast<E const&>(*this); }
}; // class expression
template<typename T, class Allocator = std::allocator<T>>
class vector
: public expression<vector<T>>
{
private:
using data_type = std::vector<T, Allocator>;
data_type m_data;
public:
using value_type = T;
using allocator_type = Allocator;
using size_type = typename data_type::size_type;
using difference_type = typename data_type::difference_type;
using reference = typename data_type::reference;
using const_reference = typename data_type::const_reference;
using pointer = typename data_type::pointer ;
using const_pointer = typename data_type::const_pointer;
vector(size_type d)
: m_data(d)
{ }
vector(std::initializer_list<value_type> init)
: m_data(init)
{ }
template<class E>
vector(expression<E> const& expression)
: m_data(expression.size())
{
for (size_type i = 0; i < expression.size(); ++i)
m_data[i] = expression[i];
}
size_type size() const {
return m_data.size();
}
value_type operator[](size_type i) const { return m_data[i]; }
value_type& operator[](size_type i) { return m_data[i]; };
}; // class vector
namespace detail
{
template<typename T>
class scalar
: public expression<scalar<T>>
{
public:
using value_type = T;
using allocator_type = std::allocator<void>;
using size_type = typename std::allocator<T>::size_type;
using difference_type = typename std::allocator<T>::difference_type;
using reference = typename std::allocator<T>::reference;
using const_reference = typename std::allocator<T>::const_reference;
using pointer = typename std::allocator<T>::pointer;
using const_pointer = typename std::allocator<T>::const_pointer;
scalar(value_type value)
: m_value(value)
{ }
size_type size() const {
return 0;
}
operator value_type&() { return static_cast<value_type&>(*this); }
operator value_type const&() const { return static_cast<value_type const&>(*this); }
value_type operator[](size_type i) const { return m_value; }
value_type& operator[](size_type i) { return m_value; }
private:
value_type m_value;
}; // class scalar
template<class>
struct is_scalar : std::false_type { };
template<class T>
struct is_scalar<scalar<T>> : std::true_type { };
} // namespace detail
template<class E1, class E2, class BinaryOperation>
class vector_binary_operation
: public expression<vector_binary_operation<E1, E2, BinaryOperation>>
{
public:
using value_type = decltype(BinaryOperation()(typename E1::value_type(), typename E2::value_type()));
using allocator_type = std::conditional_t<
detail::is_scalar<E1>::value,
typename E2::allocator_type::template rebind<value_type>::other,
typename E1::allocator_type::template rebind<value_type>::other>;
private:
using vector_type = vector<value_type, allocator_type>;
public:
using size_type = typename vector_type::size_type;
using difference_type = typename vector_type::difference_type;
using reference = typename vector_type::reference;
using const_reference = typename vector_type::const_reference;
using pointer = typename vector_type::pointer;
using const_pointer = typename vector_type::const_pointer;
vector_binary_operation(expression<E1> const& e1, expression<E2> const& e2, BinaryOperation op)
: m_e1(e1), m_e2(e2),
m_op(op)
{
if (e1.size() > 0 && e2.size() > 0 && !(e1.size() == e2.size()))
throw std::logic_error("");
}
size_type size() const {
return m_e1.size(); // == m_e2.size()
}
value_type operator[](size_type i) const {
return m_op(m_e1[i], m_e2[i]);
}
private:
E1 m_e1;
E2 m_e2;
//E1 const& m_e1;
//E2 const& m_e2;
BinaryOperation m_op;
}; // class vector_binary_operation
template<class E1, class E2>
vector_binary_operation<E1, E2, std::plus<>>
operator+(expression<E1> const& e1, expression<E2> const& e2) {
return{ e1, e2, std::plus<>() };
}
template<class E1, class E2>
vector_binary_operation<E1, E2, std::minus<>>
operator-(expression<E1> const& e1, expression<E2> const& e2) {
return{ e1, e2, std::minus<>() };
}
template<class E1, class E2>
vector_binary_operation<E1, E2, std::multiplies<>>
operator*(expression<E1> const& e1, expression<E2> const& e2) {
return{ e1, e2, std::multiplies<>() };
}
template<class E1, class E2>
vector_binary_operation<E1, E2, std::divides<>>
operator/(expression<E1> const& e1, expression<E2> const& e2) {
return{ e1, e2, std::divides<>() };
}
template<class E, typename T>
vector_binary_operation<E, detail::scalar<T>, std::divides<>>
operator/(expression<E> const& expr, T val) {
return{ expr, detail::scalar<T>(val), std::divides<>() };
}
template<class E, typename T>
vector_binary_operation<E, detail::scalar<T>, std::multiplies<>>
operator*(T val, expression<E> const& expr) {
return{ expr, detail::scalar<T>(val), std::multiplies<>() };
}
template<class E, typename T>
vector_binary_operation<E, detail::scalar<T>, std::multiplies<>>
operator*(expression<E> const& expr, T val) {
return{ expr, detail::scalar<T>(val), std::multiplies<>() };
}
} // namespace math
This allows the operations +, -, /, * for two vectors of the same size as well as the multiplication a * x and x * a of a vector x and a value a. Moreover, we can divide x / a, but not a / x (since that makes no sense). I think that this is the most plausible solution.
However, there are still some issues: I've added an Allocator template parameter to the vector class. In vector_binary_operation I need to know the resulting vector type. It would be possible that both expressions have a different allocator_type. I've decided to choose the allocator_type of the first expression in vector_binary_operation. I don't think that this is a real problem, since I don't think that it would make much sense to use different Allocators in this scenario.
The bigger issue is that I don't know how I need to deal with the expression member variables in vector_binary_operation. It would make sense to declare them as const references, cause the whole point of the code is to avoid unnecessary copies. However, as Barry pointed out, if we write sum = a + b + c, we will end up keeping a reference to the temporary a + b. Doing sum[0] will call operator()[0] on that temporary. But that object was deleted after the previous line.
I don't know what I need to do here and asked another question.
Make a specializattion of template<class E> class vector_expression for E=double.
Add in concept of min/max size (as doubles are 0 to infinite dimension), and maybe is_scalar (might not be needed, except cast-to-scalar?).
Kill vector_sum and take its toys. Make binop_vector that takes a elementop on the elements.
Add
friend binop_vector<vector_expression,R,ElemAdd>
operator+( vector_expression const& l. R const& r ){
return {l,r};
}
friend binop_vector<vector_expression<double>,vector_expression,ElemAdd>
operator+( double const& l. vector_expression const const& r ){
return {l,r};
}
And similar for * with ElemMult to vector_expression. Ths binop uses the element wise operation to implement [].
Change asserts to ensure you have overlapping min/max sizes. Report intersection in binop_vector.
The vector_expression<double> overload has a min 0 max -1 (size_t) and always returns its value. If you need more than one scalar type, write scalar_expression<T> and inherit vector_expression<double> (etc) from it.
The above is not tested, just written on phone as I sit in garage.
I'm still new to c++ and trying to understand the Expression Templates. I came across an example code on Wikipedia. I Understood most of the program and how it works but I'm not clear how these lines are interpreted by compiler:
operator A&() { return static_cast< A&>(*this); }
operator A const&() const { return static_cast<const A&>(*this); }
from the base expression template class below.
Usually the syntax of operator overloading is return_datatype operator+ (args){body} (e.g for + operator) but this gives errors and the ones in the function compiles without any error. Can anybody explain these two lines? What does A& and A const& before the operators do? And why A& operator() (){} and A const& operator() (){} doesn't work? It gives error:
no matching function for call to 'Vec::Vec(const Expr<Vec>&)'
ExprSum(const Expr<A>& a, const Expr<B>& b): _u(a), _v(b) {}
-Pranav
The complete code:
#include <iostream>
#include <vector>
#include <cassert>
using namespace std;
template <class A>
class Expr{
public:
typedef std::vector<double> container_type;
typedef typename container_type::size_type size_type;
typedef typename container_type::value_type value_type;
typedef typename container_type::reference reference;
size_type size() const {return static_cast<A const&>(*this).size(); }
value_type operator [] (size_t i) const {return static_cast<A const&> (*this)[i];}
operator A&() { return static_cast< A&>(*this); }
operator A const&() const { return static_cast<const A&>(*this); }
};
class Vec : public Expr<Vec> {
private:
container_type x;
public:
Vec(){}
Vec(size_type length) :x(length) {}
size_type size() const { return x.size(); }
reference operator [] (size_type i){
assert(i < x.size());
return x[i];
}
value_type operator [] (size_type i) const {
assert(i < x.size());
return x[i];
}
template <class A>
void operator = (const Expr<A>& ea){
x.resize(ea.size());
for(size_t i = 0; i < x.size(); i++){
x[i] = ea[i];
}
}
};
template <class A, class B>
class ExprSum : public Expr <ExprSum <A,B> >{
private:
A _u;
B _v;
public:
typedef Vec::size_type size_type;
typedef Vec::value_type value_type;
ExprSum(const Expr<A>& a, const Expr<B>& b): _u(a), _v(b) {}
value_type operator [] (size_t i) const { return (_u[i] + _v[i]); }
size_type size() const { return _u.size(); }
};
template <class A, class B>
ExprSum <A,B> const operator + (Expr<A> const& u, Expr<B> const& v){
return ExprSum <A,B> (u,v);
}
int main(){
size_t n = 10;
Vec x(n);
Vec y(n);
Vec z;
for(size_t i = 0; i < n; i++){
x[i] = i;
y[i] = 2*i;
}
z = x + y;
cout << z[7] << endl;
cout << "Hello world!" << endl;
return 0;
}
This is a conversion operator. It looks similar to a normal overloaded operator, but it doesn't have a specified return type, and in place of the operator symbol you have the conversion target type.
For supercomputing simulation purpose, I have a structure that contains two big (billions of elements) std::vector: one std::vector of "keys" (64 bits integers) and one std::vector of "values". I cannot use a std::map because in the simulations I consider, vectors are far more optimal than std::map. Moreover, I cannot use a vector of pairs because of some optimization and cache efficiency provided by separate vectors. Moreover I cannot use any extra memory.
So, considering these constaints, what is the most optimized way to sort the two vectors by increasing values of the keys ? (template metaprogramming and crazy compile-time tricks are welcome)
Two ideas off the top of my head:
Take a quicksort implementation and apply it to the "key" vector; but modify the code so that every time it does a swap on the key vector, it also performs the same swap on the value vector.
Or, perhaps more in keeping with the spirit of C++, write a custom "wrapper" iterator which iterates over both vectors at once (returning a std::pair when dereferenced). Perhaps Boost has one? You could then combine this with std::sort and a custom comparison function which considers only the "key".
EDIT:
I've used the first suggestion here for a similar problem back in a past life as a C programmer. It's far from ideal for obvious reasons, but it's possibly the quickest way to get something going.
I haven't tried a wrapper iterator like this with std::sort, but TemplateRex in the comments says it won't work, and I'm happy to defer to him on that one.
I think problem may be splitted into 2 independent parts:
How to make effective iterator for virtual map
Which sorting alorithm to use
Iterator
Implementing iterator the main problem how to return pair of key/value not creating
unnecessary copies. We can achieve it by using different types for value_type & reference. My implementation is here.
template <typename _Keys, typename _Values>
class virtual_map
{
public:
typedef typename _Keys::value_type key_type;
typedef typename _Values::value_type mapped_type;
typedef std::pair<key_type, mapped_type> value_type;
typedef std::pair<key_type&, mapped_type&> proxy;
typedef std::pair<const key_type&, const mapped_type&> const_proxy;
class iterator :
public boost::iterator_facade < iterator, value_type, boost::random_access_traversal_tag, proxy >
{
friend class boost::iterator_core_access;
public:
iterator(virtual_map *map_, size_t offset_) :
map(map_),
offset(offset_)
{}
iterator(const iterator &other_)
{
this->map = other_.map;
this->offset = other_.offset;
}
private:
bool equal(const iterator &other) const
{
assert(this->map == other.map);
return this->offset == other.offset;
}
void increment() { ++offset; }
void decrement() { --offset; }
void advance(difference_type n) { offset += n; }
reference dereference() const { return reference(map->keys[offset], map->values[offset]); }
difference_type distance_to(const iterator &other_) const { return other_.offset - this->offset; }
private:
size_t offset;
virtual_map *map;
};
public:
virtual_map(_Keys &keys_, _Values &values_) :
keys(keys_),
values(values_)
{
if(keys_.size() != values_.size())
throw std::runtime_error("different size");
}
public:
iterator begin() { return iterator(this, 0); }
iterator end() { return iterator(this, keys.size()); }
protected:
_Keys &keys;
_Values &values;
};
usage sample:
int main(int argc, char* const argv[])
{
std::vector<int> keys_ = { 17, 2, 13, 4, 51, 78, 49, 37, 1 };
std::vector<std::string> values_ = { "17", "2", "13", "4", "51", "78", "49", "37", "1" };
typedef virtual_map<std::vector<int>, std::vector<std::string>> map;
map map_(keys_, values_);
std::sort(std::begin(map_), std::end(map_), [](map::const_proxy left_, map::const_proxy right_)
{
return left_.first < right_.first;
});
return 0;
}
Sorting algorithm
Its very hard to reason which method better without additional details. What memory restriction do you have? Is it possible to use concurrency?
There are some issues:
Iterating both sequences together requires a pair representing
references to the sequence elements - that pair, itself, is no
reference. Hence, algorithms working on references will not work.
Performance will degenerate (the sequences are loosely coupled) -
An implementation using a pair of references and std::sort:
// Copyright (c) 2014 Dieter Lucking. Distributed under the Boost
// software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <algorithm>
#include <chrono>
#include <memory>
#include <iostream>
// None
// ============================================================================
/// A void type
struct None {
None()
{}
/// Explicit conversion to None.
template <typename T>
explicit None(const T&)
{}
template <typename T>
None& operator = (const T&) {
return *this;
}
/// Never null.
None* operator & () const;
};
extern None& none();
inline None* None::operator & () const { return &none(); }
None& none() {
static None result;
return result;
}
// IteratorAdaptorTraits
// ============================================================================
namespace Detail {
// IteratorAdaptorTraits
// =====================
template <typename Iterator, typename ReturnType, bool IsReference>
struct IteratorAdaptorTraits;
// No reference
// ============
template <typename Iterator, typename ReturnType>
struct IteratorAdaptorTraits<Iterator, ReturnType, false>
{
typedef Iterator iterator_type;
typedef ReturnType return_type;
typedef ReturnType value_type;
typedef None reference;
typedef None pointer;
static_assert(
! std::is_base_of<None, return_type>::value,
"None as return type.");
template <typename Accessor>
static return_type iterator_value(const Accessor& accessor, const Iterator& iterator) {
return accessor.value(iterator);
}
template <typename Accessor>
static pointer iterator_pointer(const Accessor& accessor, const Iterator& iterator) {
return &none();
}
};
// Reference
// =========
template <typename Iterator, typename ReturnType>
struct IteratorAdaptorTraits<Iterator, ReturnType, true>
{
typedef Iterator iterator_type;
typedef ReturnType return_type;
typedef typename std::remove_reference<ReturnType>::type value_type;
typedef ReturnType reference;
typedef value_type* pointer;
static_assert(
! std::is_base_of<None, return_type>::value,
"None as return type.");
template <typename Accessor>
static return_type iterator_value(const Accessor& accessor, const Iterator& iterator) {
return accessor.value(iterator);
}
template <typename Accessor>
static pointer iterator_pointer(const Accessor& accessor, const Iterator& iterator) {
return &accessor.value(iterator);
}
};
} // namespace Detail
// RandomAccessIteratorAdaptor
// ============================================================================
/// An adaptor around a random access iterator.
/// \ATTENTION The adaptor will not fulfill the standard iterator requierments,
/// if the accessor does not support references: In that case, the
/// reference and pointer type are None.
template <typename Iterator, typename Accessor>
class RandomAccessIteratorAdaptor
{
// Types
// =====
private:
static_assert(
! std::is_base_of<None, Accessor>::value,
"None as accessor.");
static_assert(
! std::is_base_of<None, typename Accessor::return_type>::value,
"None as return type.");
typedef typename Detail::IteratorAdaptorTraits<
Iterator,
typename Accessor::return_type,
std::is_reference<typename Accessor::return_type>::value
> Traits;
public:
typedef typename Traits::iterator_type iterator_type;
typedef Accessor accessor_type;
typedef typename std::random_access_iterator_tag iterator_category;
typedef typename std::ptrdiff_t difference_type;
typedef typename Traits::return_type return_type;
typedef typename Traits::value_type value_type;
typedef typename Traits::reference reference;
typedef typename Traits::pointer pointer;
typedef typename accessor_type::base_type accessor_base_type;
typedef RandomAccessIteratorAdaptor<iterator_type, accessor_base_type> base_type;
// Tag
// ===
public:
struct RandomAccessIteratorAdaptorTag {};
// Construction
// ============
public:
explicit RandomAccessIteratorAdaptor(
iterator_type iterator, const accessor_type& accessor = accessor_type())
: m_iterator(iterator), m_accessor(accessor)
{}
template <typename IteratorType, typename AccessorType>
explicit RandomAccessIteratorAdaptor(const RandomAccessIteratorAdaptor<
IteratorType, AccessorType>& other)
: m_iterator(other.iterator()), m_accessor(other.accessor())
{}
// Element Access
// ==============
public:
/// The underlaying accessor.
const accessor_type& accessor() const { return m_accessor; }
/// The underlaying iterator.
const iterator_type& iterator() const { return m_iterator; }
/// The underlaying iterator.
iterator_type& iterator() { return m_iterator; }
/// The underlaying iterator.
operator iterator_type () const { return m_iterator; }
/// The base adaptor.
base_type base() const {
return base_type(m_iterator, m_accessor.base());
}
// Iterator
// ========
public:
return_type operator * () const {
return Traits::iterator_value(m_accessor, m_iterator);
}
pointer operator -> () const {
return Traits::iterator_pointer(m_accessor, m_iterator);
}
RandomAccessIteratorAdaptor increment() const {
return ++RandomAccessIteratorAdaptor(*this);
}
RandomAccessIteratorAdaptor increment_n(difference_type n) const {
RandomAccessIteratorAdaptor tmp(*this);
tmp.m_iterator += n;
return tmp;
}
RandomAccessIteratorAdaptor decrement() const {
return --RandomAccessIteratorAdaptor(*this);
}
RandomAccessIteratorAdaptor decrement_n(difference_type n) const {
RandomAccessIteratorAdaptor tmp(*this);
tmp.m_iterator -= n;
return tmp;
}
RandomAccessIteratorAdaptor& operator ++ () {
++m_iterator;
return *this;
}
RandomAccessIteratorAdaptor operator ++ (int) {
RandomAccessIteratorAdaptor tmp(*this);
++m_iterator;
return tmp;
}
RandomAccessIteratorAdaptor& operator += (difference_type n) {
m_iterator += n;
return *this;
}
RandomAccessIteratorAdaptor& operator -- () {
--m_iterator;
return *this;
}
RandomAccessIteratorAdaptor operator -- (int) {
RandomAccessIteratorAdaptor tmp(*this);
--m_iterator;
return tmp;
}
RandomAccessIteratorAdaptor& operator -= (difference_type n) {
m_iterator -= n;
return *this;
}
bool equal(const RandomAccessIteratorAdaptor& other) const {
return this->m_iterator == other.m_iterator;
}
bool less(const RandomAccessIteratorAdaptor& other) const {
return this->m_iterator < other.m_iterator;
}
bool less_equal(const RandomAccessIteratorAdaptor& other) const {
return this->m_iterator <= other.m_iterator;
}
bool greater(const RandomAccessIteratorAdaptor& other) const {
return this->m_iterator > other.m_iterator;
}
bool greater_equal(const RandomAccessIteratorAdaptor& other) const {
return this->m_iterator >= other.m_iterator;
}
private:
iterator_type m_iterator;
accessor_type m_accessor;
};
template <typename Iterator, typename Accessor>
inline RandomAccessIteratorAdaptor<Iterator, Accessor> operator + (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& i,
typename RandomAccessIteratorAdaptor<Iterator, Accessor>::difference_type n) {
return i.increment_n(n);
}
template <typename Iterator, typename Accessor>
inline RandomAccessIteratorAdaptor<Iterator, Accessor> operator - (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& i,
typename RandomAccessIteratorAdaptor<Iterator, Accessor>::difference_type n) {
return i.decrement_n(n);
}
template <typename Iterator, typename Accessor>
inline typename RandomAccessIteratorAdaptor<Iterator, Accessor>::difference_type
operator - (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& a,
const RandomAccessIteratorAdaptor<Iterator, Accessor>& b) {
return a.iterator() - b.iterator();
}
template <typename Iterator, typename Accessor>
inline bool operator == (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& a,
const RandomAccessIteratorAdaptor<Iterator, Accessor>& b) {
return a.equal(b);
}
template <typename Iterator, typename Accessor>
inline bool operator != (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& a,
const RandomAccessIteratorAdaptor<Iterator, Accessor>& b) {
return ! a.equal(b);
}
template <typename Iterator, typename Accessor>
inline bool operator < (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& a,
const RandomAccessIteratorAdaptor<Iterator, Accessor>& b) {
return a.less(b);
}
template <typename Iterator, typename Accessor>
inline bool operator <= (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& a,
const RandomAccessIteratorAdaptor<Iterator, Accessor>& b) {
return a.less_equal(b);
}
template <typename Iterator, typename Accessor>
inline bool operator > (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& a,
const RandomAccessIteratorAdaptor<Iterator, Accessor>& b) {
return a.greater(b);
}
template <typename Iterator, typename Accessor>
inline bool operator >= (
const RandomAccessIteratorAdaptor<Iterator, Accessor>& a,
const RandomAccessIteratorAdaptor<Iterator, Accessor>& b) {
return a.greater_equal(b);
}
// ElementPair
// ============================================================================
/// A pair of references which can mutate to a pair of values.
/// \NOTE If the key is one or two the pair is less comparable
/// regarding the first or second element.
template <typename First, typename Second, unsigned Key = 0>
class ElementPair
{
// Types
// =====
public:
typedef First first_type;
typedef Second second_type;
// Construction
// ============
public:
/// Reference
/// \POSTCONDITION reference() returns true
ElementPair(first_type& first, second_type& second)
: m_first(&first), m_second(&second)
{}
/// Copy construction
/// \POSTCONDITION reference() returns false
ElementPair(const ElementPair& other)
: m_first(new(m_first_storage) first_type(*other.m_first)),
m_second(new(&m_second_storage) second_type(*other.m_second))
{}
/// Move construction
/// \POSTCONDITION reference() returns false
ElementPair(ElementPair&& other)
: m_first(new(m_first_storage) first_type(std::move(*other.m_first))),
m_second(new(m_second_storage) second_type(std::move(*other.m_second)))
{}
~ElementPair() {
if( ! reference()) {
reinterpret_cast<first_type*>(m_first_storage)->~first_type();
reinterpret_cast<second_type*>(m_second_storage)->~second_type();
}
}
// Assignment
// ==========
public:
/// Swap content.
void swap(ElementPair& other) {
std::swap(*m_first, *other.m_first);
std::swap(*m_second, *other.m_second);
}
/// Assign content.
ElementPair& operator = (const ElementPair& other) {
if(&other != this) {
*m_first = *other.m_first;
*m_second = *other.m_second;
}
return *this;
}
/// Assign content.
ElementPair& operator = (ElementPair&& other) {
if(&other != this) {
*m_first = std::move(*other.m_first);
*m_second = std::move(*other.m_second);
}
return *this;
}
// Element Access
// ==============
public:
/// True if the pair holds references to external elements.
bool reference() {
return (m_first != reinterpret_cast<first_type*>(m_first_storage));
}
const first_type& first() const { return *m_first; }
first_type& first() { return *m_first; }
const second_type& second() const { return *m_second; }
second_type& second() { return *m_second; }
private:
first_type* m_first;
typename std::aligned_storage<
sizeof(first_type),
std::alignment_of<first_type>::value>::type
m_first_storage[1];
second_type* m_second;
typename std::aligned_storage<
sizeof(second_type),
std::alignment_of<second_type>::value>::type
m_second_storage[1];
};
// Compare
// =======
template <typename First, typename Second>
inline bool operator < (
const ElementPair<First, Second, 1>& a,
const ElementPair<First, Second, 1>& b)
{
return (a.first() < b.first());
}
template <typename First, typename Second>
inline bool operator < (
const ElementPair<First, Second, 2>& a,
const ElementPair<First, Second, 2>& b)
{
return (a.second() < b.second());
}
// Swap
// ====
namespace std {
template <typename First, typename Second, unsigned Key>
inline void swap(
ElementPair<First, Second, Key>& a,
ElementPair<First, Second, Key>& b)
{
a.swap(b);
}
}
// SequencePairAccessor
// ============================================================================
template <typename FirstSequence, typename SecondSequence, unsigned Keys = 0>
class SequencePairAccessor
{
// Types
// =====
public:
typedef FirstSequence first_sequence_type;
typedef SecondSequence second_sequence_type;
typedef typename first_sequence_type::size_type size_type;
typedef typename first_sequence_type::value_type first_type;
typedef typename second_sequence_type::value_type second_type;
typedef typename first_sequence_type::iterator iterator;
typedef None base_type;
typedef ElementPair<first_type, second_type, Keys> return_type;
// Construction
// ============
public:
SequencePairAccessor(first_sequence_type& first, second_sequence_type& second)
: m_first_sequence(&first), m_second_sequence(&second)
{}
// Element Access
// ==============
public:
base_type base() const { return base_type(); }
return_type value(iterator pos) const {
return return_type(*pos, (*m_second_sequence)[pos - m_first_sequence->begin()]);
}
// Data
// ====
private:
first_sequence_type* m_first_sequence;
second_sequence_type* m_second_sequence;
};
This test shows a degenaration of performance (on my system) by a factor of 1.5 for const char* and a factor of 3.4 for a std::string (compared to a single vector holding std::pair(s)).
// Test
// ============================================================================
#define SAMPLE_SIZE 1e1
#define VALUE_TYPE const char*
int main() {
const unsigned samples = SAMPLE_SIZE;
typedef int key_type;
typedef VALUE_TYPE value_type;
typedef std::vector<key_type> key_sequence_type;
typedef std::vector<value_type> value_sequence_type;
typedef SequencePairAccessor<key_sequence_type, value_sequence_type, 1> accessor_type;
typedef RandomAccessIteratorAdaptor<
key_sequence_type::iterator,
accessor_type>
iterator_adaptor_type;
key_sequence_type keys;
value_sequence_type values;
keys.reserve(samples);
values.reserve(samples);
const char* words[] = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };
for(unsigned i = 0; i < samples; ++i) {
key_type k = i % 10;
keys.push_back(k);
values.push_back(words[k]);
}
accessor_type accessor(keys, values);
std::random_shuffle(
iterator_adaptor_type(keys.begin(), accessor),
iterator_adaptor_type(keys.end(), accessor)
);
if(samples <= 10) {
std::cout << "\nRandom:\n"
<< "======\n";
for(unsigned i = 0; i < keys.size(); ++i)
std::cout << keys[i] << ": " << values[i] << '\n';
}
typedef std::pair<key_type, value_type> pair_type;
std::vector<pair_type> ref;
for(const auto& k: keys) {
ref.push_back(pair_type(k, words[k]));
}
struct Less {
bool operator () (const pair_type& a, const pair_type& b) const {
return a.first < b.first;
}
};
auto ref_start = std::chrono::system_clock::now();
std::sort(ref.begin(), ref.end(), Less());
auto ref_end = std::chrono::system_clock::now();
auto ref_elapsed = double((ref_end - ref_start).count())
/ std::chrono::system_clock::period::den;
auto start = std::chrono::system_clock::now();
std::sort(
iterator_adaptor_type(keys.begin(), accessor),
iterator_adaptor_type(keys.end(), accessor)
);
auto end = std::chrono::system_clock::now();
auto elapsed = double((end - start).count())
/ std::chrono::system_clock::period::den;;
if(samples <= 10) {
std::cout << "\nSorted:\n"
<< "======\n";
for(unsigned i = 0; i < keys.size(); ++i)
std::cout << keys[i] << ": " << values[i] << '\n';
}
std::cout << "\nDuration sorting " << double(samples) << " samples:\n"
<< "========\n"
<< " One Vector: " << ref_elapsed << '\n'
<< "Two Vectors: " << elapsed << '\n'
<< " Factor: " << elapsed/ref_elapsed << '\n'
<< '\n';
}
(Please adjust SAMPLE_SIZE and VALUE_TYPE)
My conclusion is a sorted view into a sequence of unsorted data might be more aprropiate (but that violates the requirement of the question).
I'm trying to write a C++ version of Python's itertools.tee for Boost::Range (as seen here). Here is my first attempt:
template<typename R>
class tee_iterator : std::iterator<std::forward_iterator_tag, typename boost::range_value<R>::type>
{
public:
typedef typename boost::range_value<R>::type T;
typedef std::list<T> tee_queue;
typedef std::vector<tee_queue> tee_queue_collection;
tee_iterator(const R& r, tee_queue* q, tee_queue_collection* qs) :
it_(r.begin()), queue_(q), queues_(qs) {}
tee_iterator(const R& r) : it_(r.end()), queue_(NULL), queues_(NULL) {}
T& operator*() const { return current_; }
tee_iterator& operator++()
{
if (queue_->empty()) {
++it_;
for (auto q : queues_) {
q->push_back(*it_);
}
}
current_ = queue_->front();
queue_->pop_front();
return *this;
}
bool operator==(tee_iterator const& o) const { return it_ == o.it_; }
bool operator!=(tee_iterator const& o) const { return !(*this == o); }
private:
typedef typename boost::range_iterator<const R>::type const_iterator;
const_iterator it_;
tee_queue* queue_;
tee_queue_collection* queues_;
T current_;
};
template<typename R>
using tee_range = boost::iterator_range<tee_iterator<R> >;
template<typename R>
std::list<tee_range<R> > tee(const R& r, int n)
{
typedef typename boost::range_value<R>::type T;
typedef std::list<T> tee_queue;
typedef std::vector<tee_queue> tee_queue_collection;
tee_queue_collection queues(n);
std::list<tee_range<R> > ranges;
for (int i = 0; i < n; ++i) {
tee_range<R> t = { tee_iterator<R>(r, &queues[i], &queues), tee_iterator<R>(r) };
ranges.push_back(t);
}
return ranges;
}
but as soon as I try to use it:
int main(int argc, char* argv[])
{
std::list<int> l;
for (int i = 0; i < 10; ++i) {
l.push_back(i);
}
auto t = tee(l, 3);
}
it blows in my face, complaining about a missing value_type in boost::detail::iterator_traits<tee_iterator<std::list<int, std::allocator<int> > > > (and others). What am I missing? Isn't tee_iterator being a child of std::iterator enough?
You forgot to add public:
class tee_iterator : public std::iterator...