I'm studying c++ templates and reading <<C++ Templates: The Complete Guide>>. I don't understand the flowing about expression template:
The code as following:
//exprarray.h
#include <stddef.h>
#include <cassert>
#include "sarray.h"
template<typename T>
class A_Scale
{
public:
A_Scale(T const& t):value(t){}
T operator[](size_t) const
{
return value;
}
size_t size() const
{
return 0;
}
private:
T const& value;
};
template<typename T>
class A_Traits
{
public:
typedef T const& exprRef;
};
template<typename T>
class A_Traits<A_Scale<T> >
{
public:
typedef A_Scale<T> exprRef;
};
template<typename T,typename L1,typename R2>
class A_Add
{
private:
typename A_Traits<L1>::exprRef op1;
typename A_Traits<R2>::exprRef op2;
public:
A_Add(L1 const& a,R2 const& b):op1(a),op2(b)
{
}
T operator[](size_t indx) const
{
return op1[indx] + op2[indx];
}
size_t size() const
{
assert(op1.size()==0 || op2.size()==0 || op1.size() == op2.size());
return op1.size() != 0 ? op1.size() : op2.size();
}
};
template<typename T,typename L1,typename R2>
class A_Mul
{
private:
typename A_Traits<L1>::exprRef op1;
typename A_Traits<R2>::exprRef op2;
public:
A_Mul(L1 const& a,R2 const& b):op1(a),op2(b)
{
}
T operator[](size_t indx) const
{
return op1[indx] * op2[indx];
}
size_t size() const
{
assert(op1.size()==0 || op2.size()==0 || op1.size() == op2.size());
return op1.size() != 0 ? op1.size():op2.size();
}
};
template<typename T,typename Rep = SArray<T> >
class Array
{
public:
explicit Array(size_t N):expr_Rep(N){}
Array(Rep const& rep):expr_Rep(rep){}
Array& operator=(Array<T> const& orig)
{
assert(size() == orig.size());
for (size_t indx=0;indx < orig.size();indx++)
{
expr_Rep[indx] = orig[indx];
}
return *this;
}
template<typename T2,typename Rep2>
Array& operator=(Array<T2,Rep2> const& orig)
{
assert(size() == orig.size());
for (size_t indx=0;indx<orig.size();indx++)
{
expr_Rep[indx] = orig[indx];
}
return *this;
}
size_t size() const
{
return expr_Rep.size();
}
T operator[](size_t indx) const
{
assert(indx < size());
return expr_Rep[indx];
}
T& operator[](size_t indx)
{
assert(indx < size());
return expr_Rep[indx];
}
Rep const& rep() const
{
return expr_Rep;
}
Rep& rep()
{
return expr_Rep;
}
private:
Rep expr_Rep;
};
template<typename T,typename L1,typename R2>
Array<T,A_Add<T,L1,R2> >
operator+(Array<T,L1> const& a,Array<T,R2> const& b)
{
return Array<T,A_Add<T,L1,R2> >(A_Add<T,L1,R2>(a.rep(),b.rep()));
}
template<typename T,typename L1,typename R2>
Array<T,A_Mul<T,L1,R2> >
operator*(Array<T,L1> const& a,Array<T,R2> const& b)
{
return Array<T,A_Mul<T,L1,R2> >(A_Mul<T,L1,R2>(a.rep(),b.rep()));
}
template<typename T,typename R2>
Array<T,A_Mul<T,A_Scale<T>,R2> >
operator*(T const& a,Array<T,R2> const& b)
{
return Array<T,A_Mul<T,A_Scale<T>,R2> >(A_Mul<T,A_Scale<T>,R2>(A_Scale<T>(a),b.rep()));
}
The test code:
//test.cpp
#include "exprarray.h"
#include <iostream>
using namespace std;
template <typename T>
void print (T const& c)
{
for (int i=0; i<8; ++i) {
std::cout << c[i] << ' ';
}
std::cout << "..." << std::endl;
}
int main()
{
Array<double> x(1000), y(1000);
for (int i=0; i<1000; ++i) {
x[i] = i;
y[i] = x[i]+x[i];
}
std::cout << "x: ";
print(x);
std::cout << "y: ";
print(y);
x = 1.2 * x;
std::cout << "x = 1.2 * x: ";
print(x);
x = 1.2*x + x*y;
std::cout << "1.2*x + x*y: ";
print(x);
x = y;
std::cout << "after x = y: ";
print(x);
return 0;
}
My questions is why A_Traits for A_Scale is by value not by reference.
template<typename T>
class A_Traits
{
public:
typedef T const& exprRef;
};
template<typename T>
class A_Traits<A_Scale<T> >
{
public:
typedef A_Scale<T> exprRef;
};
The reason from the book as following:
This is necessary because of the following: In general, we can declare them to be references because most temporary nodes are bound in the top-level expression and therefore live until the end of the evaluation of that complete expression. The one exception are the A_Scalar nodes. They are bound within the operator functions and might not live until the end of the evaluation of the complete expression. Thus, to avoid that the members refer to scalars that don't exist anymore, for scalars the operands have to get copied "by value."
More detail please refer to the chapter 18 of C++ Templates: The Complete Guide
Consider, for example, the right hand side of
x = 1.2*x + x*y;
What the quote says is that this is composed of two different categories.
The heavy array x and y objects are not defined within this expression, but rather before it:
Array<double> x(1000), y(1000);
So, as you build expressions using them, you don't have to worry whether they're still alive - they were defined beforehand. Since they're heavy, you want to capture them by reference, and, fortunately, their lifetime makes that possible.
Conversely, the lightweight A_Scale objects are generated within the expression (e.g., implicitly by the 1.2 above). Since they're temporaries, you have to worry about their lifetime. Since they're lightweight, it's not a problem.
That's the rationale for the traits class differentiating between them: the former are by reference, and the latter are by value (they are copied).
Related
I want to make a generic print(x) function, which behaves different for different types.
What I have so far works for all container types, including the one I wrote myself. However, either the "wrong" function is getting called or it won't compile due to ambiguity.
Here is my code:
#include <iostream>
#include <concepts>
class Container
{
int x, y, z;
public:
Container(int a, int b, int c) : x(a), y(b), z(c) {}
Container() : x(0), y(0), z(0) {}
std::size_t size() const { return 3; }
const int& operator[] (std::size_t index) const { if(index == 0) return x; else if(index == 1) return y; else return z; }
int& operator[] (std::size_t index) { if(index == 0) return x; else if(index == 1) return y; else return z; }
};
template<typename T>
concept printable_num = requires (T t, std::size_t s)
{
{ t.size() } -> std::convertible_to<std::size_t>;
{ t[s] } -> std::same_as<int&>;
};
template<printable_num T>
void print(const T& t) {
std::size_t i = 0;
for(;i < t.size() - 1; i++)
std::cout << t[i] << ", ";
std::cout << t[i] << std::endl;
}
template<typename T>
concept printable = requires (T t, std::size_t s)
{
{ t.size() } -> std::convertible_to<std::size_t>;
{ t[s] } -> std::convertible_to<char>;
};
template<printable T>
void print(const T& t) {
std::size_t i = 0;
for(;i < t.size() - 1; i++)
std::cout << t[i];
std::cout << t[i] << std::endl;
}
int main()
{
Container c{1, 2, 3};
print(c);
Container empty;
print(empty);
std::string s{"this is some string"};
print(s);
return 0;
}
As you can see, I want to print a separator if the type returned from operator[] is int&. This does not compile due to ambiguity.
Is there a way to make this compile and to get me where I want (call the print function without the separator for std::string and the one with separator for my own Container type)?
Given an integer (or lvalue to one), would you not agree that it is convertible to a char? The constraints check exactly what you have them check for the types in your question.
One way to tackle it would be by constraint subsumption. Meaning (in a very hand wavy fashion) that if your concepts are written as a conjugation (or disjunction) of the same basic constraints, a compiler can normalize the expression to choose the "more specialized one". Applying it to your example..
template<typename T>
concept printable = requires (T t, std::size_t s)
{
{ t.size() } -> std::convertible_to<std::size_t>;
{ t[s] } -> std::convertible_to<char>;
};
template<typename T>
concept printable_num = printable<T> && requires (T t, std::size_t s)
{
{ t[s] } -> std::same_as<int&>;
};
Note how we used printable to define printable_num as the "more specific" concept. Running this example, we get the output you are after.
I am trying to wrap my head around expression templates. In the wikipedia article, an example is given, where an expression template VecSum stores const references to its two operands. A Vec is an expression template that wraps an std::vector<double>. I will first pose my question and then give a complete rundown of the example below.
Can I re-use expressions that use const references to temporaries? And if not, how would I implement light-weight, re-useable expression templates?
For three Vecs a, b, and c the expression a+b+c is of type
VecSum<VecSum<Vec, Vec>, Vec>
If I understand correctly, the inner VecSum is a temporary and the outer VecSum stores a const reference to the inner VecSum. I believe the lifetime of the inner VecSum temporary is guaranteed until the expression a+b+c gets evaluated. Correct? Does this mean that the expression cannot be reused without the danger of creating dangling references?
auto expr = a + b + c;
Vec v1 = expr; // ok
Vec v2 = expr; // not ok!
If so, how can this example be modified, so that
the expressions are reusable
the expressions do not store copies of their operands (at least in situations where it is not necessary)?
Full code example
For completeness - and in case the wikipedia article is updated in the meantime, let me repeat the example code here and give an example in the main that I believe creates a dangling reference.
#include <cassert>
#include <vector>
template <typename E>
class VecExpression {
public:
double operator[](size_t i) const
{
// Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
return static_cast<E const&>(*this)[i];
}
size_t size() const { return static_cast<E const&>(*this).size(); }
};
class Vec : public VecExpression<Vec> {
std::vector<double> elems;
public:
double operator[](size_t i) const { return elems[i]; }
double &operator[](size_t i) { return elems[i]; }
size_t size() const { return elems.size(); }
Vec(size_t n) : elems(n) {}
// construct vector using initializer list
Vec(std::initializer_list<double> init) : elems(init) {}
// A Vec can be constructed from any VecExpression, forcing its evaluation.
template <typename E>
Vec(VecExpression<E> const& expr) : elems(expr.size()) {
for (size_t i = 0; i != expr.size(); ++i) {
elems[i] = expr[i];
}
}
};
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2> > {
E1 const& _u;
E2 const& _v;
public:
VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
assert(u.size() == v.size());
}
double operator[](size_t i) const { return _u[i] + _v[i]; }
size_t size() const { return _v.size(); }
};
template <typename E1, typename E2>
VecSum<E1, E2>
operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) {
return VecSum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}
int main() {
Vec v0 = {23.4,12.5,144.56,90.56};
Vec v1 = {67.12,34.8,90.34,89.30};
Vec v2 = {34.90,111.9,45.12,90.5};
auto expr = v0 + v1 + v2;
Vec v1 = expr; // ok
Vec v2 = expr; // not ok!
}
Edit:
I just realized this might be a duplicate of this question. However the answers to both questions are very different and all usefull.
The comment above has a very effective way to check the problem with the dangling reference. Note that if you try to print the values from the main function in your example the program will still work because the object that will have the dangling reference bound to it will be created also on the stack space of main. I tried to move the code which is assigned to expr inside a function and the program crashed as expected (the temporary object will be in another stack frame):
auto makeExpr1(Vec const& v0, Vec const& v1, Vec const& v2) {
return v0 + v1 + v2;
}
// ... in main:
auto expr = makeExpr1(v0, v1, v2);
The problem you highlighted here appears in the cases of creating an expression that can be lazily evaluated in languages like C++. A somehow similar situation can occur in the context of range expressions (C++20 ranges).
Below is my quick attempt to fix that code and make it work with lvalues and rvalues added with the operator + (I apologise for the ugly parts and possible mistakes). This will store copy of their operands only when they are going to be out of scope and will result in dangling references in the old code.
Regarding re-usability: as long as you define a type for every operation and a corresponding operator '?' function ('?' being the simbol of the operation) this approch should give you a starting point for any binary operation on such a vector.
#include <cassert>
#include <vector>
#include <utility>
#include <iostream>
/*
* Passes lvalues and stores rvalues
*/
template <typename T> class Wrapper;
template <typename T> class Wrapper<T&> {
private:
T& ref;
public:
Wrapper(T& ref) : ref(ref) {}
T& get() { return ref; }
const T& get() const { return ref; }
};
template <typename T> class Wrapper<T&&> {
private:
T value;
public:
Wrapper(T&& ref) : value(std::move(ref)) {}
T& get() { return value; }
const T& get() const { return value; }
};
template <typename E>
class VecExpression {
public:
double operator[](size_t i) const
{
// Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
return static_cast<E const&>(*this)[i];
}
size_t size() const { return static_cast<E const&>(*this).size(); }
};
/*
* Forwards the reference and const qualifiers
* of the expression type to the expression itself
*/
template <typename E> constexpr E& forwardRef(VecExpression<E>& ve) {
return static_cast<E&>(ve);
}
template <typename E> constexpr const E& forwardRef(const VecExpression<E>& ve) {
return static_cast<const E&>(ve);
}
template <typename E> constexpr E&& forwardRef(VecExpression<E>&& ve) {
return static_cast<E&&>(ve);
}
class Vec : public VecExpression<Vec> {
std::vector<double> elems;
public:
double operator[](size_t i) const { return elems[i]; }
double &operator[](size_t i) { return elems[i]; }
size_t size() const { return elems.size(); }
Vec(size_t n) : elems(n) {}
// construct vector using initializer list
Vec(std::initializer_list<double> init) : elems(init) {}
// A Vec can be constructed from any VecExpression, forcing its evaluation.
template <typename E>
Vec(VecExpression<E> const& expr) : elems(expr.size()) {
std::cout << "Expr ctor\n"; // Very quick test
for (size_t i = 0; i != expr.size(); ++i) {
elems[i] = expr[i];
}
}
// Move ctor added for checking
Vec(Vec&& vec) : elems(std::move(vec.elems)) {
std::cout << "Move ctor\n"; // Very quick test
}
};
/*
* Now VecSum is a sum between possibly const - qualified
* and referenced expression types
*/
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2>> {
Wrapper<E1> _u;
Wrapper<E2> _v;
public:
VecSum(E1 u, E2 v) : _u(static_cast<E1>(u)), _v(static_cast<E2>(v)) {
assert(_u.get().size() == _v.get().size());
}
double operator[](size_t i) const { return _u.get()[i] + _v.get()[i]; }
size_t size() const { return _v.get().size(); }
};
/*
* Used to create a VecSum by capturing also the reference kind
* of the arguments (will be used by the Wrapper inside VecSum)
*/
template <typename E1, typename E2>
auto makeVecSum(E1&& e1, E2&& e2) {
return VecSum<E1&&, E2&&>(std::forward<E1>(e1), std::forward<E2>(e2));
}
/*
* Now the operator+ takes the vector expressions by universal references
*/
template <typename VE1, typename VE2>
auto operator+(VE1&& ve1, VE2&& ve2) {
return makeVecSum(forwardRef(std::forward<VE1>(ve1)), forwardRef(std::forward<VE2>(ve2)));
}
// Now this will work
auto makeExpr1(Vec const& v0, Vec const& v1, Vec const& v2) {
return v0 + v1 + v2;
}
// This will also work - the rvalue is stored in the
// expression itself and both will have the same lifetime
auto makeExpr2(Vec const& v0, Vec const& v1) {
return v0 + v1 + Vec({1.0, 1.0, 1.0, 1.0});
}
int main() {
Vec v0 = {23.4,12.5,144.56,90.56};
Vec v1 = {67.12,34.8,90.34,89.30};
Vec v2 = {34.90,111.9,45.12,90.5};
auto expr = makeExpr1(v0, v1, v2);
Vec v1_ = expr;
Vec v2_ = expr;
auto expr_ = makeExpr2(v0, v1);
for (size_t i = 0; i < v1_.size(); ++i)
std::cout << v1_[i] << " ";
std::cout << std::endl;
for (size_t i = 0; i < v2_.size(); ++i)
std::cout << v2_[i] << " ";
std::cout << std::endl;
for (size_t i = 0; i < expr.size(); ++i)
std::cout << expr[i] << " ";
std::cout << std::endl;
for (size_t i = 0; i < expr_.size(); ++i)
std::cout << expr_[i] << " ";
std::cout << std::endl;
}
I have a pointer-like struct that goes in the place of a pointer.
The difference with a pointer is that it has extra information that the (also special) allocator can use to deallocate the memory.
This pointer-like structure works well for all basic uses.
I can allocate and deallocate memory, dereferrence, increment,->, etc.
Now I want to use this pointers to be managed by a STL-like container.
Early on, I realized that STL vector basically cannot handle non-raw pointers.
T* is too hard coded, and the standard basically rules out anything that is not a pointer.
Inspired by Boost.Interprocess' offset_ptr<T> I decided to use Boost.Container vector, which is very customizable and in principle can manage anything, the allocator passed to the boost::container::vector can handle anything that is pointer-like.
Now the class boost::container::vector<T, myallocator_with_special_pointer<T>> can do anything... except resize()!!
Looking at the code in boost/container/vector.hpp it seems that the process of resizing (which is basically and allocation, followed by a copy (or move) and deallocation) involves raw pointers.
The offending line is:
[line 2729:] T * const new_buf = container_detail::to_raw_pointer
(allocator_traits_type::allocate(this->m_holder.alloc(), new_cap, this->m_holder.m_start));
Which is later followed by
[line 3022:] this->m_holder.start(new_start); // new_start is the same as new_buf above.
// member ::start(pointer&) will need to convert a raw pointer to the pointer typedef.
Both lines absolutely kill the possibility of using anything that is not a raw_pointer. Even if I have a conversion operator to a raw pointer, other information about the special pointer will be lost.
It seems pretty silly that this small detail forbids the use of non-raw pointers. Given all the effort for the container to be general (e.g. defining the pointer typedef), why this portion of the code uses T* just for resizing?
In other words, why Boost Container doesn't use this line instead
[alternative] pointer const new_buf =
allocator_traits_type::allocate(this->m_holder.alloc(), new_cap, this->m_holder.m_start);
Is there a workaround or an alternative way to use Boost Container vector to handle non-raw pointers?
Boost.Container says in its manual page http://www.boost.org/doc/libs/1_64_0/doc/html/container/history_and_reasons.html#container.history_and_reasons.Why_boost_container
Boost.Container is a product of a long development effort that started
in 2004 with the experimental Shmem library, which pioneered the use
of standard containers in shared memory. Shmem included modified SGI
STL container code tweaked to support non-raw allocator::pointer types
and stateful allocators. Once reviewed, Shmem was accepted as
Boost.Interprocess and this library continued to refine and improve
those containers.
The current implementation (in the context of resize) goes against this design goal.
I asked a less specific question here, about other traits of the allocators: Is it still possible to customize STL vector's "reference" type?
For reference the allocator that specifies the special pointer (which is propagated to the container) is something like this,
template<class T>
struct allocator{
using value_type = T;
using pointer = array_ptr<T>; // simulates T*
using const_pointer = array_ptr<T const>; // simulates T const*
using void_pointer = array_ptr<void>; // simulates void*
using const_void_pointer = array_ptr<void const>; // simulates void const*
some_managed_shared_memory& msm_;
allocator(some_managed_shared_memory& msm) : msm_(msm){}
array_ptr<T> allocate(mpi3::size_t n){
auto ret = msm_.allocate(n*sizeof(T));
return static_cast<array_ptr<T>>(ret);
}
void deallocate(array_ptr<T> ptr, mpi3::size_t = 0){
msm_.deallocate(ptr);
}
};
Full working code http://coliru.stacked-crooked.com/a/f43b6096f9464cbf
#include<iostream>
#include <boost/container/vector.hpp>
template<typename T>
struct array_ptr;
template<>
struct array_ptr<void> {
using T = void;
T* p;
int i; //some additional information
// T& operator*() const { return *p; }
T* operator->() const { return p; }
// operator T*() const { return p; }
template<class TT>
operator array_ptr<TT>() const{return array_ptr<TT>((TT*)p, i);}
operator bool() const{return p;}
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr){}
array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
template<class Other>
array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};
template<>
struct array_ptr<void const> {
using T = void const;
T* p;
int i; //some additional information
// T& operator*() const { return *p; }
T* operator->() const { return p; }
operator T*() const { return p; }
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr){}
array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
template<class Other>
array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};
template<typename T>
struct array_ptr {
T* p;
int i; //some additional information
T& operator*() const { return *p; }
T* operator->() const { return p; }
T& operator[](std::size_t n) const{
assert(i == 99);
return *(p + n);
}
bool operator==(array_ptr const& other) const{return p == other.p and i == other.i;}
bool operator!=(array_ptr const& other) const{return not((*this)==other);}
// operator T*() const { return p; }
array_ptr& operator++(){++p; return *this;}
array_ptr& operator+=(std::ptrdiff_t n){p+=n; return *this;}
array_ptr& operator-=(std::ptrdiff_t n){p-=n; return *this;}
array_ptr operator+(std::size_t n) const{array_ptr ret(*this); ret+=n; return ret;}
std::ptrdiff_t operator-(array_ptr const& other) const{return p - other.p;}
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr), i(0){}
operator bool() const{return p;}
array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
array_ptr(T* ptr) : p(ptr), i(0){}
array_ptr(int) : p(nullptr), i(0){}
array_ptr(array_ptr<void> const& other) : p(static_cast<T*>(other.p)), i(other.i){}
};
struct some_managed_shared_memory {
array_ptr<void> allocate(size_t n) { return array_ptr<void>(::malloc(n), 99); }
void deallocate(array_ptr<void> ptr) { if (ptr) ::free(ptr.p); }
};
template<typename T>
struct allocator{
using value_type = T;
using pointer = array_ptr<T>; // simulates T*
using const_pointer = array_ptr<T const>; // simulates T const*
using void_pointer = array_ptr<void>; // simulates void*
using const_void_pointer = array_ptr<void const>; // simulates void const*
some_managed_shared_memory& msm_;
allocator(some_managed_shared_memory& msm) : msm_(msm){}
array_ptr<T> allocate(size_t n){
auto ret = msm_.allocate(n*sizeof(T));
return static_cast<array_ptr<T>>(ret);
}
void deallocate(array_ptr<T> ptr, std::size_t = 0){
msm_.deallocate(ptr);
}
};
int main() {
some_managed_shared_memory realm;
boost::container::vector<int, allocator<int> > v(10, realm);
assert( v[4] == 0 );
v[4] = 1;
assert( v[4] == 1 );
for(std::size_t i = 0; i != v.size(); ++i) std::cout << v[i] << std::endl;
for(auto it = v.begin(); it != v.end(); ++it) std::cout << *it << std::endl;
// none of these compile:
v.push_back(8);
assert(v.size() == 11);
v.resize(100);
std::cout << v[89] << std::endl; // will fail an assert because the allocator information is lost
//v.assign({1,2,3,4,5});
}
I looked into things.
The TL;DR seems to be: non-raw pointers are supported, but they need a implicit conversion from raw in some operations. Whether or not this is by design, I don't know, but it doesn't seem to contradict the design goal.
In fact this is very analogous to the history of allocator support: STL containers had support for custom allocators, but not for stateful allocators (meaning, non-default-constructible allocator types).
Allocator Versions
At first I tried some of the allocator versions:
using version = boost::container::version_0; // seems unsupported, really
using version = boost::container::version_1;
using version = boost::container::version_2; // does different operations
But it had no (decisive) effect. Maybe the documentation has clues.
Pointer Arithmetic
After that I looked into the specific errors. Looking at the cited line/error it dawned on me that the raw-pointer might have been an accident. Looking at the output of these:
std::cout << boost::container::container_detail::impl::version<allocator<int> >::value << "\n";
array_ptr<int> p;
auto rawp = boost::container::container_detail::to_raw_pointer(p);
std::cout << typeid(rawp).name() << "\n";
std::cout << typeid(p).name() << "\n";
std::cout << typeid(p + 5).name() << "\n";
std::cout << typeid(p - 5).name() << "\n";
Shows something like¹
1
int*
array_ptr<int>
int*
int*
¹ prettified with the help of c++filt -t
This lead me to define pointer arithmetic:
template <typename T, typename N>
array_ptr<T> operator+(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p+n, p.i); }
template <typename T>
array_ptr<T>& operator++(array_ptr<T>& p) { return ++p.p, p; }
template <typename T>
array_ptr<T> operator++(array_ptr<T>& p, int) { auto q = p.p++; return array_ptr<T>(q, p.i); }
template <typename T, typename N>
array_ptr<T> operator-(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p-n, p.i); }
template <typename T>
ptrdiff_t operator-(array_ptr<T> const& a, array_ptr<T> const& b) { return a.p - b.p; }
Now the output becomes
1
int*
array_ptr<int>
array_ptr<int>
array_ptr<int>
Many more use cases compile successfully with these definitions. Assuming that the "annotation" data inside the array_pointer is valid after increment, it should not lose any allocator information
The Real Culprit
With that out of the way, some things still don't compile. Specifically, in some spots the allocator's pointer type is constructed back from a raw-pointer. This fails because there's no suitable "default" conversion constructor. If you declare the constructors with the data value optional, everything compiles, but you could argue that this loses information as there is a path from
array_pointer<T> p;
auto* rawp = to_raw_pointer(p);
array_pointer<T> clone(rawp); // oops lost the extra info in p
OBSERVATION
Note that, as you apparently realized (judging from the commented operators), adding the default constructor argument removes the need for the arithmetic operations (except pre-increment).
However, adding them makes sure that the lossy conversion path is taken less often, which could be important to your use case.
DEMO TIME
Live On Coliru
#if COMPILATION_INSTRUCTIONS
clang++ -std=c++14 -Wall -Wfatal-errors $0 -o $0x.x && $0x.x $# && rm -f $0x.x; exit
#endif
#define DEFAULT_DATA = 0
#define DEFINE_ARITHMETIC_OPERATIONS
#include <iostream>
#include <boost/container/vector.hpp>
#include <typeinfo>
template<typename T>
struct array_ptr {
T* p;
int i; //some additional information
T& operator*() const { return *p; }
T* operator->() const { return p; }
operator T*() const { return p; }
array_ptr(){}
//array_ptr(std::nullptr_t) : p(nullptr), i(0){}
array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}
};
template<>
struct array_ptr<void> {
using T = void;
T* p;
int i; //some additional information
// T& operator*() const { return *p; }
T* operator->() const { return p; }
operator T*() const { return p; }
template<class T>
operator array_ptr<T>() const{return array_ptr<T>((T*)p, i);}
// array_ptr& operator++(){++p; return *this;}
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr){}
array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}
template<class Other>
array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};
template<>
struct array_ptr<void const> {
using T = void const;
T* p;
int i; //some additional information
// T& operator*() const { return *p; }
T* operator->() const { return p; }
operator T*() const { return p; }
// array_ptr& operator++(){++p; return *this;}
// template<class Other> array_ptr(array_ptr<Other> const& other) : p(other.p), i(other.i){}
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr){}
array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}
template<class Other>
array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};
struct some_managed_shared_memory {
array_ptr<void> allocate(size_t n) { return array_ptr<void>(::malloc(n), 99); }
void deallocate(array_ptr<void> ptr) { if (ptr) ::free(ptr.p); }
};
template<typename T>
struct allocator{
using version = boost::container::version_1;
using value_type = T;
using pointer = array_ptr<T>; // simulates T*
using const_pointer = array_ptr<T const>; // simulates T const*
using void_pointer = array_ptr<void>; // simulates void*
using const_void_pointer = array_ptr<void const>; // simulates void const*
some_managed_shared_memory& msm_;
allocator(some_managed_shared_memory& msm) : msm_(msm){}
array_ptr<T> allocate(size_t n){
auto ret = msm_.allocate(n*sizeof(T));
return static_cast<array_ptr<T>>(ret);
}
void deallocate(array_ptr<T> ptr, std::size_t = 0){
msm_.deallocate(ptr);
}
};
#ifdef DEFINE_ARITHMETIC_OPERATIONS
template <typename T, typename N>
array_ptr<T> operator+(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p+n, p.i); }
template <typename T>
array_ptr<T>& operator++(array_ptr<T>& p) { return ++p.p, p; }
template <typename T>
array_ptr<T> operator++(array_ptr<T>& p, int) { auto q = p.p++; return array_ptr<T>(q, p.i); }
template <typename T, typename N>
array_ptr<T> operator-(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p-n, p.i); }
template <typename T>
ptrdiff_t operator-(array_ptr<T> const& a, array_ptr<T> const& b) { return a.p - b.p; }
#endif
int main() {
std::cout << boost::container::container_detail::impl::version<allocator<int> >::value << "\n";
if (1) { // some diagnostics
array_ptr<int> p;
auto rawp = boost::container::container_detail::to_raw_pointer(p);
std::cout << typeid(rawp).name() << "\n";
std::cout << typeid(p).name() << "\n";
std::cout << typeid(p + 5).name() << "\n";
std::cout << typeid(p - 5).name() << "\n";
}
some_managed_shared_memory realm;
boost::container::vector<int, allocator<int> > v(10, realm);
assert( v[4] == 0 );
v[4] = 1;
assert( v[4] == 1 );
for(std::size_t i = 0; i != v.size(); ++i) std::cout << v[i] << std::endl;
// these compile:
v.push_back(12);
v.resize(100);
v.assign({1,2,3,4,5});
}
Prints
1
Pi
9array_ptrIiE
9array_ptrIiE
9array_ptrIiE
0
0
0
0
1
0
0
0
0
0
The code below allows me to template a function
taking a parameter which is a vector of one of three different pointer types to Box objects:
const std::vector<std::shared_ptr<Box>>&
const std::vector<std::weak_ptr<Box>>&
const std::vector<Box*>&
Is there a way to extend this to support:
const vector<Box>&
const vector<std::reference_wrapper<Box>>
perhaps something in boost?
#include <vector>
#include <iostream>
class Box{
public:
Box (unsigned int id, unsigned int side): id(id), side(side){}
int volume(){
return side * side * side;
}
unsigned int id;
unsigned int side;
};
template <typename T>
struct is_box_containter {
enum { value = false };
};
template <>
struct is_box_containter <std::vector<std::shared_ptr<Box>>> {
enum { value = true };
};
template <>
struct is_box_containter <std::vector<std::weak_ptr<Box>>> {
enum { value = true };
};
template <>
struct is_box_containter <std::vector<Box*>> {
enum { value = true };
};
template <typename T>
typename std::enable_if<is_box_containter<T>::value>::type
measure(T const& boxes )
{
for (auto& box : boxes) {
std::cout << box->id << " has volume " << box->volume() << std::endl;
}
}
int main (){
std::vector<std::shared_ptr<Box>> some_boxes;
some_boxes.push_back(std::shared_ptr<Box>(new Box(1,4)));
some_boxes.emplace_back(new Box(2, 12));
Box * box_3 = new Box(3, 8);
Box * box_4 = new Box(4, 9);
std::vector<Box*> more_boxes;
more_boxes.emplace_back(box_3);
more_boxes.emplace_back(box_4);
measure(some_boxes);
measure(more_boxes);
return 0;
}
Why I am asking this question:
I have an application with two functions which implement near identical logic. One takes a list of SomeClass, the other takes a vector of pointers to SomeClass.
I am currently planning on refactoring the code to replace the list of SomeClass with a list of shared pointers to SomeClass. But the only reason I am doing this is to move the logic to a common implementation. I don't want to do that if there is a perfectly reasonable way to avoid it.
If I understood your question correctly, you could use a dereferencing mechanism like below:
template<typename T>
T& dereference(T &v) {
return v;
}
template<typename T>
const T& dereference(const T& v) {
return v;
}
template<typename T>
typename std::enable_if<!std::is_function<T>::value, T&>::type dereference(T* v) {
return dereference(*v);
}
template<typename T>
const T& dereference(const std::shared_ptr<T>& v) {
return dereference(*v);
}
template<typename T>
const T& dereference(const std::weak_ptr<T>& v) {
return dereference(*v);
}
template<typename T>
const T& dereference(const std::reference_wrapper<T>& v) {
return v;
}
and then call your data like:
template <typename T>
typename std::enable_if<is_box_containter<T>::value>::type
measure(T const& boxes )
{
for (auto& box : boxes) {
std::cout << dereference(box).id
<< " has volume " << dereference(box).volume() << std::endl;
}
}
LIVE DEMO
P.S You'll also have to define:
template <>
struct is_box_containter <std::vector<Box>> {
enum { value = true };
};
template <>
struct is_box_containter <std::vector<std::reference_wrapper<Box>>> {
enum { value = true };
};
Say if I had a vector<string> already defined and filled called test and an int called a. If I wanted to combine these 2 into a single object called combined where i could do combined[0] = test; to initialize/retrieve the object with the vector and combined[1] = a; to initialize/retrieve the object with the int, what would be the best function to do so and how would I do so? I had attempted to do vector<vector<string>, int> but this gave me an error.
Note: I am compiling with -std=c++11 if this matters.
Use a std::tuple<std::vector<std::string>,int>.
#include <tuple>
#include <vector>
#include <string>
int main() {
std::vector<std::string> test;
int a{};
std::tuple<std::vector<std::string>,int> combined;
//To access elements, use `std::get`:
std::get<0>(combined) = test;
std::get<1>(combined) = a;
}
to answer cellsheet's comment: that function already exists, it's called std::make_tuple() (see also comment by fjardon on how to store this).
Btw, why do you need to extend std::vector<std::string> by an int?
If I understand correctly what you're asking, I think you can do this with a std::pair:
std::pair<std::vector<std::string>, int> combined;
combined.first = test; // assign vector
combined.second = a; // assign int
or simply
auto combined = std::make_pair(test,a);
It requires (ugly) type elision:
#include <iostream>
#include <stdexcept>
#include <type_traits>
#include <vector>
class X {
public:
typedef std::vector<std::string> vector_type;
typedef int integer_type;
private:
enum Type {
TypeVector,
TypeInteger
};
template <bool Constant>
class Proxy
{
private:
typedef typename std::conditional<
Constant, const void, void>::type void_t;
public:
typedef typename std::conditional<
Constant, const vector_type, vector_type>::type vector_t;
typedef typename std::conditional<
Constant, const integer_type, integer_type>::type integer_t;
Proxy(vector_t& v)
: m_type(TypeVector), m_data(&v)
{}
Proxy(integer_t& i)
: m_type(TypeInteger), m_data(&i)
{}
operator vector_t& () const {
if(m_type != TypeVector) throw std::runtime_error("Invalid Type");
return *static_cast<vector_t*>(m_data);
}
operator integer_t& () const {
if(m_type != TypeInteger) throw std::runtime_error("Invalid Type");
return *static_cast<integer_t*>(m_data);
}
private:
template <typename T, typename U, bool> struct Assignment
{
static void apply(void_t*, const U&) {}
};
template <typename T, typename U>
struct Assignment<T, U, true>
{
static void apply(void_t* p, const U& value) {
*static_cast<T*>(p) = value;
}
};
template <typename T, typename U>
// Attention: Use a reference - std::is_assignable<int, int>::value> is false;
struct Assign : Assignment<T, U, std::is_assignable<T&, U>::value>
{};
public:
template <typename U>
Proxy&
operator = (const U& value) {
static_assert( ! Constant, "Assignment to Constant");
switch(m_type) {
case TypeVector:
Assign<vector_t, U>::apply(m_data, value);
break;
case TypeInteger:
Assign<integer_t, U>::apply(m_data, value);
break;
default: throw std::out_of_range("Invalid Type");
}
return *this;
}
private:
Type m_type;
void_t* m_data;
};
public:
X() : m_v{"Hello"}, m_i(1) {}
Proxy<true> operator [] (std::size_t i) const {
switch(i) {
case 0: return Proxy<true>(m_v);
case 1: return Proxy<true>(m_i);
default: throw std::out_of_range("Invalid Index");
}
}
Proxy<false> operator [] (std::size_t i) {
switch(i) {
case 0: return Proxy<false>(m_v);
case 1: return Proxy<false>(m_i);
default: throw std::out_of_range("Invalid Index");
}
}
private:
vector_type m_v;
integer_type m_i;
};
int main() {
// Note: The Proxy has no operator []
// const
{
const X x;
const X::vector_type& v = x[0];
std::cout << v[0] << " " << x[1] << std::endl;
}
// non const
{
X x;
X::vector_type& v = x[0];
v[0] = "World";
x[1] = 2;
std::cout << v[0] << " " << x[1] << std::endl;
}
}
You might consider boost::any, instead.